diff --git a/.scripts/preconstruct.ts b/.scripts/preconstruct.ts index a743cd9..fedab4e 100644 --- a/.scripts/preconstruct.ts +++ b/.scripts/preconstruct.ts @@ -1,95 +1,108 @@ -import fs from "node:fs/promises"; -import path from "node:path"; -import { glob } from "fast-glob"; +import fs from 'node:fs/promises' +import path from 'node:path' +import { glob } from 'fast-glob' // Symlinks package sources to dist for local development -console.log("Setting up packages for development."); +console.log('Setting up packages for development.') async function main() { // Get all package.json files - const packagePaths = await glob("**/package.json", { - ignore: ["**/dist/**", "**/node_modules/**"], - }); + const packagePaths = await glob('**/package.json', { + ignore: ['**/dist/**', '**/node_modules/**'], + }) - let count = 0; + let count = 0 for (const packagePath of packagePaths) { type Package = { - bin?: Record | undefined; - exports?: Record | undefined; - name?: string | undefined; - private?: boolean | undefined; - }; - const file = await fs.readFile(packagePath, "utf8"); - const packageJson = (await JSON.parse(file)) as Package; + bin?: Record | undefined + exports?: + | Record + | undefined + name?: string | undefined + private?: boolean | undefined + } + const file = await fs.readFile(packagePath, 'utf8') + const packageJson = (await JSON.parse(file)) as Package // Skip private packages - if (packageJson.private) continue; - if (!packageJson.exports) continue; + if (packageJson.private) continue + if (!packageJson.exports) continue - count += 1; - console.log(`${packageJson.name} — ${path.dirname(packagePath)}`); + count += 1 + console.log(`${packageJson.name} — ${path.dirname(packagePath)}`) - const dir = path.resolve(path.dirname(packagePath)); + const dir = path.resolve(path.dirname(packagePath)) // Empty dist directory - const distDirName = "dist"; - const dist = path.resolve(dir, distDirName); - let files: string[] = []; + const distDirName = 'dist' + const dist = path.resolve(dir, distDirName) + let files: string[] = [] try { - files = await fs.readdir(dist); + files = await fs.readdir(dist) } catch { - await fs.mkdir(dist); + await fs.mkdir(dist) } - const promises: Promise[] = []; + const promises: Promise[] = [] for (const file of files) { - promises.push(fs.rm(path.join(dist, file), { recursive: true, force: true })); + promises.push( + fs.rm(path.join(dist, file), { recursive: true, force: true }), + ) } - await Promise.all(promises); + await Promise.all(promises) // Link exports to dist locations for (const [key, exports] of Object.entries(packageJson.exports)) { // Skip `package.json` exports - if (/package\.json$/.test(key)) continue; + if (/package\.json$/.test(key)) continue - let entries: any; - if (typeof exports === "string") + let entries: any + if (typeof exports === 'string') entries = [ - ["default", exports], - ["types", exports.replace(".js", ".d.ts")], - ]; - else entries = Object.entries(exports); + ['default', exports], + ['types', exports.replace('.js', '.d.ts')], + ] + else entries = Object.entries(exports) // Link exports to dist locations - for (const [type, value] of entries as [type: "types" | "default", value: string][]) { + for (const [type, value] of entries as [ + type: 'types' | 'default', + value: string, + ][]) { const srcDir = path.resolve( dir, path .dirname(value) .replace( - `dist${packageJson.name === "@fc-auth/react" ? "" : type === "default" ? "/esm" : `/${type}`}`, - "src", + `dist${ + packageJson.name === '@fc-auth/react' + ? '' + : type === 'default' + ? '/esm' + : `/${type}` + }`, + 'src', ), - ); - let srcFileName: string; - if (key.endsWith(".css")) continue; - if (key === ".") srcFileName = "index.ts"; - else srcFileName = path.basename(`${key}.ts`); - const srcFilePath = path.resolve(srcDir, srcFileName); + ) + let srcFileName: string + if (key.endsWith('.css')) continue + if (key === '.') srcFileName = 'index.ts' + else srcFileName = path.basename(`${key}.ts`) + const srcFilePath = path.resolve(srcDir, srcFileName) - const distDir = path.resolve(dir, path.dirname(value)); - const distFileName = path.basename(value); - const distFilePath = path.resolve(distDir, distFileName); + const distDir = path.resolve(dir, path.dirname(value)) + const distFileName = path.basename(value) + const distFilePath = path.resolve(distDir, distFileName) - await fs.mkdir(distDir, { recursive: true }); + await fs.mkdir(distDir, { recursive: true }) // Symlink src to dist file - await fs.symlink(srcFilePath, distFilePath, "file"); + await fs.symlink(srcFilePath, distFilePath, 'file') } } } - console.log(`Done. Set up ${count} ${count === 1 ? "package" : "packages"}.`); + console.log(`Done. Set up ${count} ${count === 1 ? 'package' : 'packages'}.`) } -main(); +main() diff --git a/apps/relay/jest.config.ts b/apps/relay/jest.config.ts index e6383a5..fe558ce 100644 --- a/apps/relay/jest.config.ts +++ b/apps/relay/jest.config.ts @@ -1,21 +1,21 @@ -import type { Config } from "jest"; +import type { Config } from 'jest' const jestConfig: Config = { - testEnvironment: "node", - coveragePathIgnorePatterns: ["/build/", "/node_modules/"], - testPathIgnorePatterns: ["/build", "/node_modules"], - extensionsToTreatAsEsm: [".ts"], + testEnvironment: 'node', + coveragePathIgnorePatterns: ['/build/', '/node_modules/'], + testPathIgnorePatterns: ['/build', '/node_modules'], + extensionsToTreatAsEsm: ['.ts'], moduleNameMapper: { - "^(\\.{1,2}/.*)\\.js$": "$1", + '^(\\.{1,2}/.*)\\.js$': '$1', }, /** * For high performance with minimal configuration transform with TS with swc. * @see https://github.com/farcasterxyz/hub/issues/314 */ transform: { - "^.+\\.(t|j)sx?$": "@swc/jest", + '^.+\\.(t|j)sx?$': '@swc/jest', }, - maxWorkers: "50%", -}; + maxWorkers: '50%', +} -export default jestConfig; +export default jestConfig diff --git a/apps/relay/src/channel.ts b/apps/relay/src/channel.ts index bb60a5f..37d9096 100644 --- a/apps/relay/src/channel.ts +++ b/apps/relay/src/channel.ts @@ -1,89 +1,104 @@ -import { randomUUID } from "crypto"; -import { Hono } from "hono"; -import { rateLimiter } from "hono-rate-limiter"; -import { z } from "zod"; -import { generateNonce } from "siwe"; -import { fromZodError } from "zod-validation-error"; -import { auth } from "./middlewares/auth.js"; -import { channel as channelMiddleware } from "./middlewares/channel.js"; -import { getAddresses } from "./utils/getAddresses.js"; -import { getConfig } from "./utils/getConfig.js"; -import { RelayError } from "./utils/errors.js"; -import { channelCreateSchema } from "./schemas/channelCreate.js"; -import { channelAuthenticateSchema } from "./schemas/channelAuthenticate.js"; -import type { ChannelAuthenticateReturnType, ChannelCreateReturnType, ChannelGetReturnType } from "./types/actions.js"; - -const { baseUrl, authKey } = getConfig(); +import { randomUUID } from 'node:crypto' +import { Hono } from 'hono' +import { rateLimiter } from 'hono-rate-limiter' +import { generateNonce } from 'siwe' +import type { z } from 'zod' +import { fromZodError } from 'zod-validation-error' +import { auth } from './middlewares/auth.js' +import { channel as channelMiddleware } from './middlewares/channel.js' +import { channelAuthenticateSchema } from './schemas/channelAuthenticate.js' +import { channelCreateSchema } from './schemas/channelCreate.js' +import type { + ChannelAuthenticateReturnType, + ChannelCreateReturnType, + ChannelGetReturnType, +} from './types/actions.js' +import { RelayError } from './utils/errors.js' +import { getAddresses } from './utils/getAddresses.js' +import { getConfig } from './utils/getConfig.js' + +const { baseUrl, authKey } = getConfig() export const channel = new Hono().use( rateLimiter({ limit: 1000, }), channelMiddleware, -); +) function constructUrl( - parameters: { channelToken: string; nonce: string } & z.infer, + parameters: { channelToken: string; nonce: string } & z.infer< + typeof channelCreateSchema + >, ): string { - const query = new URLSearchParams(parameters); - return `${baseUrl}?${query.toString()}`; + const query = new URLSearchParams(parameters) + return `${baseUrl}?${query.toString()}` } -channel.on("POST", ["/channel", "/connect"], async (c) => { - const parseResult = channelCreateSchema.safeParse(c.req.json()); +channel.on('POST', ['/channel', '/connect'], async (c) => { + const parseResult = channelCreateSchema.safeParse(c.req.json()) if (!parseResult.success) { - c.status(400); - return c.json({ error: "Validation error", message: fromZodError(parseResult.error) }); + c.status(400) + return c.json({ + error: 'Validation error', + message: fromZodError(parseResult.error), + }) } - const body = parseResult.data; + const body = parseResult.data - const channelToken = randomUUID(); + const channelToken = randomUUID() try { - await c.var.channel.create(channelToken); + await c.var.channel.create(channelToken) - const nonce = body.nonce ?? generateNonce(); - const url = constructUrl({ channelToken, nonce, ...body }); + const nonce = body.nonce ?? generateNonce() + const url = constructUrl({ channelToken, nonce, ...body }) const pendingChannel = { - state: "pending", + state: 'pending', nonce, url, channelToken, - } as const satisfies ChannelCreateReturnType; + } as const satisfies ChannelCreateReturnType - await c.var.channel.update(channelToken, pendingChannel); - c.status(201); - return c.json(pendingChannel); + await c.var.channel.update(channelToken, pendingChannel) + c.status(201) + return c.json(pendingChannel) } catch (e) { - if (!(e instanceof RelayError)) throw new Error("Unexpected error"); + if (!(e instanceof RelayError)) throw new Error('Unexpected error') - c.status(500); - return c.json({ error: e.message }); + c.status(500) + return c.json({ error: e.message }) } -}); +}) -channel.post("/(connect|channel)/authenticate", auth, async (c) => { - const reqAuthKey = c.req.header("x-farcaster-auth-relay-key") ?? c.req.header("x-farcaster-connect-auth-key"); +channel.post('/(connect|channel)/authenticate', auth, async (c) => { + const reqAuthKey = + c.req.header('x-farcaster-auth-relay-key') ?? + c.req.header('x-farcaster-connect-auth-key') if (reqAuthKey !== authKey) { - c.status(401); - return c.json({ error: "Unauthorized" }); + c.status(401) + return c.json({ error: 'Unauthorized' }) } try { - const parseResult = channelAuthenticateSchema.safeParse(c.req.json()); + const parseResult = channelAuthenticateSchema.safeParse(c.req.json()) if (!parseResult.success) { - c.status(400); - return c.json({ error: "Validation error", message: fromZodError(parseResult.error) }); + c.status(400) + return c.json({ + error: 'Validation error', + message: fromZodError(parseResult.error), + }) } - const { message, signature, fid, username, displayName, bio, pfpUrl } = parseResult.data; + const { message, signature, fid, username, displayName, bio, pfpUrl } = + parseResult.data - const addresses = await getAddresses(fid); + const addresses = await getAddresses(fid) - const channel = await c.var.channel.get(c.var.channelToken); + const channel = await c.var.channel.get(c.var.channelToken) const result = { ...channel, - state: "completed", + state: 'completed', message, signature, fid, @@ -92,48 +107,50 @@ channel.post("/(connect|channel)/authenticate", auth, async (c) => { bio, pfpUrl, ...addresses, - } satisfies ChannelAuthenticateReturnType; - c.status(201); - return c.json(result); + } satisfies ChannelAuthenticateReturnType + c.status(201) + return c.json(result) } catch (e) { - c.status(500); + c.status(500) if (e instanceof RelayError) { - if (e.errCode === "not_found") { - c.status(401); - return c.json({ error: "Unauthorized" }); + if (e.errCode === 'not_found') { + c.status(401) + return c.json({ error: 'Unauthorized' }) } - return c.json({ error: e.message }); + return c.json({ error: e.message }) } - return c.json({ error: new RelayError("unavailable", e as Error).message }); + return c.json({ error: new RelayError('unavailable', e as Error).message }) } -}); +}) -channel.get("/(connect|channel)/status", auth, async (c) => { +channel.get('/(connect|channel)/status', auth, async (c) => { try { - const channel = (await c.var.channel.get(c.var.channelToken)) satisfies ChannelGetReturnType; - if (channel.state === "completed") { + const channel = (await c.var.channel.get( + c.var.channelToken, + )) satisfies ChannelGetReturnType + if (channel.state === 'completed') { try { - await c.var.channel.delete(c.var.channelToken); + await c.var.channel.delete(c.var.channelToken) } catch (e) { - if (!(e instanceof RelayError)) throw new Error("Unexpected error"); - c.status(500); - return c.json({ error: e.message }); + if (!(e instanceof RelayError)) throw new Error('Unexpected error') + c.status(500) + return c.json({ error: e.message }) } - return c.json(channel); + return c.json(channel) } - c.status(202); - return c.json(channel); + c.status(202) + return c.json(channel) } catch (e) { - if (!(e instanceof RelayError)) throw new Error("Unexpected error"); + if (!(e instanceof RelayError)) throw new Error('Unexpected error') - if (e.errCode === "not_found") { - c.status(401); - return c.json({ error: "Unauthorized" }); + if (e.errCode === 'not_found') { + c.status(401) + return c.json({ error: 'Unauthorized' }) } - c.status(500); - return c.json({ error: e.message }); + c.status(500) + return c.json({ error: e.message }) } -}); +}) diff --git a/apps/relay/src/exports/index.ts b/apps/relay/src/exports/index.ts index 5cf9c47..19a95fd 100644 --- a/apps/relay/src/exports/index.ts +++ b/apps/relay/src/exports/index.ts @@ -1,2 +1,2 @@ -export * from "../types/actions.js"; -export * from "../types/channel.js"; +export * from '../types/actions.js' +export * from '../types/channel.js' diff --git a/apps/relay/src/healthcheck.ts b/apps/relay/src/healthcheck.ts index a786a18..d673d8a 100644 --- a/apps/relay/src/healthcheck.ts +++ b/apps/relay/src/healthcheck.ts @@ -1,7 +1,7 @@ -import { Hono } from "hono"; +import { Hono } from 'hono' -export const healthcheck = new Hono(); +export const healthcheck = new Hono() -healthcheck.get("/", (c) => { - return c.json({ status: "OK" }); -}); +healthcheck.get('/', (c) => { + return c.json({ status: 'OK' }) +}) diff --git a/apps/relay/src/index.ts b/apps/relay/src/index.ts index 0dafff6..0eac945 100644 --- a/apps/relay/src/index.ts +++ b/apps/relay/src/index.ts @@ -1,14 +1,14 @@ -import { Hono } from "hono"; -import { cors } from "hono/cors"; -import { channel } from "./channel.js"; -import { healthcheck } from "./healthcheck.js"; -import { getConfig } from "./utils/getConfig.js"; +import { Hono } from 'hono' +import { cors } from 'hono/cors' +import { channel } from './channel.js' +import { healthcheck } from './healthcheck.js' +import { getConfig } from './utils/getConfig.js' -const config = getConfig(); +const config = getConfig() -export const app = new Hono().basePath("/v2"); +export const app = new Hono().basePath('/v2') -app.use(cors({ origin: config.corsOrigin })); +app.use(cors({ origin: config.corsOrigin })) -app.route("/healthcheck", healthcheck); -app.route("/channel", channel); +app.route('/healthcheck', healthcheck) +app.route('/channel', channel) diff --git a/apps/relay/src/middlewares/auth.ts b/apps/relay/src/middlewares/auth.ts index d54e318..991edab 100644 --- a/apps/relay/src/middlewares/auth.ts +++ b/apps/relay/src/middlewares/auth.ts @@ -1,17 +1,19 @@ -import { createMiddleware } from "hono/factory"; +import { createMiddleware } from 'hono/factory' export type AuthVariables = { - channelToken: string; -}; + channelToken: string +} -export const auth = createMiddleware<{ Variables: AuthVariables }>(async (c, next) => { - const auth = c.req.header("authorization"); +export const auth = createMiddleware<{ Variables: AuthVariables }>( + async (c, next) => { + const auth = c.req.header('authorization') - const channelToken = auth?.split(" ")[1]; - if (!channelToken) { - c.status(401); - return c.json({ error: "Unauthorized" }); - } - c.set("channelToken", channelToken); - return next(); -}); + const channelToken = auth?.split(' ')[1] + if (!channelToken) { + c.status(401) + return c.json({ error: 'Unauthorized' }) + } + c.set('channelToken', channelToken) + return next() + }, +) diff --git a/apps/relay/src/middlewares/channel.ts b/apps/relay/src/middlewares/channel.ts index c1f35ee..2fde535 100644 --- a/apps/relay/src/middlewares/channel.ts +++ b/apps/relay/src/middlewares/channel.ts @@ -1,58 +1,61 @@ -import { createMiddleware } from "hono/factory"; -import { Redis } from "ioredis"; -import { getConfig } from "../utils/getConfig.js"; -import { RelayError } from "../utils/errors.js"; -import { type Channel } from "../types/channel.js"; +import { createMiddleware } from 'hono/factory' +import { Redis } from 'ioredis' +import type { Channel } from '../types/channel.js' +import { RelayError } from '../utils/errors.js' +import { getConfig } from '../utils/getConfig.js' -const config = getConfig(); +const config = getConfig() export type ChannelVariables = { channel: { - get: (token: string) => Promise; - update: (token: string, channels: Channel) => Promise; - create: (token: string) => Promise; - delete: (token: string) => Promise; - }; -}; + get: (token: string) => Promise + update: (token: string, channels: Channel) => Promise + create: (token: string) => Promise + delete: (token: string) => Promise + } +} -const { channelTtl } = getConfig(); +const { channelTtl } = getConfig() -export const channel = createMiddleware<{ Variables: ChannelVariables }>(async (c, next) => { - const redis = new Redis(config.redisUrl); +export const channel = createMiddleware<{ Variables: ChannelVariables }>( + async (c, next) => { + const redis = new Redis(config.redisUrl) - c.set("channel", { - get: async (token) => { - try { - const serializedChannel = await redis.get(token); - if (!serializedChannel) throw new RelayError("not_found", "Channel not found"); - return JSON.parse(serializedChannel); - } catch (e) { - if (e instanceof RelayError) throw e; + c.set('channel', { + get: async (token) => { + try { + const serializedChannel = await redis.get(token) + if (!serializedChannel) + throw new RelayError('not_found', 'Channel not found') + return JSON.parse(serializedChannel) + } catch (e) { + if (e instanceof RelayError) throw e - throw new RelayError("unknown", e as Error); - } - }, - create: async (token) => { - try { - await redis.set(token, JSON.stringify({}), "EX", channelTtl); - } catch (e) { - throw new RelayError("unknown", e as Error); - } - }, - update: async (token, channels) => { - try { - await redis.set(token, JSON.stringify(channels), "EX", channelTtl); - } catch (e) { - throw new RelayError("unknown", e as Error); - } - }, - delete: async (token) => { - try { - await redis.del(token); - } catch (e) { - throw new RelayError("unknown", e as Error); - } - }, - }); - await next(); -}); + throw new RelayError('unknown', e as Error) + } + }, + create: async (token) => { + try { + await redis.set(token, JSON.stringify({}), 'EX', channelTtl) + } catch (e) { + throw new RelayError('unknown', e as Error) + } + }, + update: async (token, channels) => { + try { + await redis.set(token, JSON.stringify(channels), 'EX', channelTtl) + } catch (e) { + throw new RelayError('unknown', e as Error) + } + }, + delete: async (token) => { + try { + await redis.del(token) + } catch (e) { + throw new RelayError('unknown', e as Error) + } + }, + }) + await next() + }, +) diff --git a/apps/relay/src/schemas/channelAuthenticate.ts b/apps/relay/src/schemas/channelAuthenticate.ts index 12aa3f9..85194e4 100644 --- a/apps/relay/src/schemas/channelAuthenticate.ts +++ b/apps/relay/src/schemas/channelAuthenticate.ts @@ -1,13 +1,17 @@ -import type { Hex } from "viem"; -import { z } from "zod"; +import type { Hex } from 'viem' +import { z } from 'zod' export const channelAuthenticateSchema = z.object({ message: z.string() /*.url()?*/, signature: z.custom((val) => /^0x[a-fA-F0-9]{130}$/.test(val as string)), fid: z.number(), - username: z.string().regex(/^[a-z0-9][a-z0-9-]{0,15}$|^[a-z0-9][a-z0-9-]{0,15}\\.eth$/), + username: z + .string() + .regex(/^[a-z0-9][a-z0-9-]{0,15}$|^[a-z0-9][a-z0-9-]{0,15}\\.eth$/), bio: z.string(), displayName: z.string(), pfpUrl: z.string().url(), -}); -export type ChannelAuthenticateParameters = z.infer; +}) +export type ChannelAuthenticateParameters = z.infer< + typeof channelAuthenticateSchema +> diff --git a/apps/relay/src/schemas/channelCreate.ts b/apps/relay/src/schemas/channelCreate.ts index 4d4829b..0851096 100644 --- a/apps/relay/src/schemas/channelCreate.ts +++ b/apps/relay/src/schemas/channelCreate.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from 'zod' export const channelCreateSchema = z.object({ siweUri: z.string().url(), @@ -8,6 +8,6 @@ export const channelCreateSchema = z.object({ expirationTime: z.string().optional(), requestId: z.string().optional(), redirectUrl: z.string().optional(), -}); +}) -export type ChannelCreateParameters = z.infer; +export type ChannelCreateParameters = z.infer diff --git a/apps/relay/src/types/actions.ts b/apps/relay/src/types/actions.ts index faa55f9..0823e03 100644 --- a/apps/relay/src/types/actions.ts +++ b/apps/relay/src/types/actions.ts @@ -1,12 +1,14 @@ -import type { z } from "zod"; -import type { CompletedChannel, PendingChannel, Channel } from "./channel.js"; -import type { channelCreateSchema } from "../schemas/channelCreate.js"; -import type { channelAuthenticateSchema } from "../schemas/channelAuthenticate.js"; +import type { z } from 'zod' +import type { channelAuthenticateSchema } from '../schemas/channelAuthenticate.js' +import type { channelCreateSchema } from '../schemas/channelCreate.js' +import type { Channel, CompletedChannel, PendingChannel } from './channel.js' -export type ChannelCreateParameters = z.infer; -export type ChannelCreateReturnType = PendingChannel & { channelToken: string }; +export type ChannelCreateParameters = z.infer +export type ChannelCreateReturnType = PendingChannel & { channelToken: string } -export type ChannelAuthenticateParameters = z.infer; -export type ChannelAuthenticateReturnType = CompletedChannel; +export type ChannelAuthenticateParameters = z.infer< + typeof channelAuthenticateSchema +> +export type ChannelAuthenticateReturnType = CompletedChannel -export type ChannelGetReturnType = Channel; +export type ChannelGetReturnType = Channel diff --git a/apps/relay/src/types/channel.ts b/apps/relay/src/types/channel.ts index a195350..7c8992d 100644 --- a/apps/relay/src/types/channel.ts +++ b/apps/relay/src/types/channel.ts @@ -1,19 +1,19 @@ -import { type Hex } from "viem"; +import type { Hex } from 'viem' -export type PendingChannel = { state: "pending"; nonce: string; url: string }; +export type PendingChannel = { state: 'pending'; nonce: string; url: string } export type CompletedChannel = { - state: "completed"; - message: string; - signature: `0x${string}`; - fid: number; - username: string; - bio: string; - displayName: string; - pfpUrl: string; - verifications: Hex[]; - custody: Hex; - nonce: string; - url: string; -}; + state: 'completed' + message: string + signature: `0x${string}` + fid: number + username: string + bio: string + displayName: string + pfpUrl: string + verifications: Hex[] + custody: Hex + nonce: string + url: string +} -export type Channel = PendingChannel | CompletedChannel; +export type Channel = PendingChannel | CompletedChannel diff --git a/apps/relay/src/types/verifications.ts b/apps/relay/src/types/verifications.ts index 84c91ec..7df60d2 100644 --- a/apps/relay/src/types/verifications.ts +++ b/apps/relay/src/types/verifications.ts @@ -1,25 +1,27 @@ -import { type Hex } from "viem"; +import type { Hex } from 'viem' export type VerificationAddAddressBody = { - address: Hex; -}; + address: Hex +} export type ArbitraryVerificationMessageData = { - verificationAddEthAddressBody: never; - verificationAddAddressBody: VerificationAddAddressBody; -}; + verificationAddEthAddressBody: never + verificationAddAddressBody: VerificationAddAddressBody +} export type EthVerificationMessageData = { - verificationAddEthAddressBody: VerificationAddAddressBody; - verificationAddAddressBody: never; -}; + verificationAddEthAddressBody: VerificationAddAddressBody + verificationAddAddressBody: never +} -export type VerificationMessageData = ArbitraryVerificationMessageData | EthVerificationMessageData; +export type VerificationMessageData = + | ArbitraryVerificationMessageData + | EthVerificationMessageData export type VerificationMessage = { - data: VerificationMessageData; -}; + data: VerificationMessageData +} export type VerificationsAPIResponse = { - messages: VerificationMessage[]; -}; + messages: VerificationMessage[] +} diff --git a/apps/relay/src/utils/errors.ts b/apps/relay/src/utils/errors.ts index df6436a..9303d8b 100644 --- a/apps/relay/src/utils/errors.ts +++ b/apps/relay/src/utils/errors.ts @@ -1,41 +1,44 @@ interface RelayErrorOpts { - message: string; - cause: Error | RelayError; - presentable: boolean; + message: string + cause: Error | RelayError + presentable: boolean } export class RelayError extends Error { - public readonly errCode: RelayErrorCode; + public readonly errCode: RelayErrorCode /* Indicates if error message can be presented to the user */ - public readonly presentable: boolean = false; + public readonly presentable: boolean = false - public override readonly cause: RelayErrorOpts["cause"] | undefined; + public override readonly cause: RelayErrorOpts['cause'] | undefined /** * @param errCode - the ConnectError code for this message * @param context - a message, another Error, or a ConnectErrorOpts */ - constructor(errCode: RelayErrorCode, context: Partial | string | Error) { - let parsedContext: string | Error | Partial; + constructor( + errCode: RelayErrorCode, + context: Partial | string | Error, + ) { + let parsedContext: string | Error | Partial - if (typeof context === "string") { - parsedContext = { message: context }; + if (typeof context === 'string') { + parsedContext = { message: context } } else if (context instanceof Error) { - parsedContext = { cause: context, message: context.message }; + parsedContext = { cause: context, message: context.message } } else { - parsedContext = context; + parsedContext = context } if (!parsedContext.message) { - parsedContext.message = parsedContext.cause?.message || ""; + parsedContext.message = parsedContext.cause?.message || '' } - super(parsedContext.message); + super(parsedContext.message) - this.cause = parsedContext.cause; - this.name = "ConnectError"; - this.errCode = errCode; + this.cause = parsedContext.cause + this.name = 'ConnectError' + this.errCode = errCode } } @@ -48,17 +51,17 @@ export class RelayError extends Error { */ export type RelayErrorCode = /* The request did not have valid authentication credentials, retry with credentials */ - | "unauthenticated" + | 'unauthenticated' /* The authenticated request did not have the authority to perform this action */ - | "unauthorized" + | 'unauthorized' /* The request cannot be completed as constructed, do not retry */ - | "bad_request" - | "bad_request.validation_failure" + | 'bad_request' + | 'bad_request.validation_failure' /* The requested resource could not be found */ - | "not_found" + | 'not_found' /* The request could not be completed because the operation is not executable */ - | "not_implemented" + | 'not_implemented' /* The request could not be completed, it may or may not be safe to retry */ - | "unavailable" + | 'unavailable' /* An unknown error was encountered */ - | "unknown"; + | 'unknown' diff --git a/apps/relay/src/utils/getAddresses.ts b/apps/relay/src/utils/getAddresses.ts index 56957f6..b77fc7d 100644 --- a/apps/relay/src/utils/getAddresses.ts +++ b/apps/relay/src/utils/getAddresses.ts @@ -1,7 +1,10 @@ -import { getCustodyAddress } from "./getCustodyAddress.js"; -import { getVerifiedAddresses } from "./getVerifiedAddresses.js"; +import { getCustodyAddress } from './getCustodyAddress.js' +import { getVerifiedAddresses } from './getVerifiedAddresses.js' export async function getAddresses(fid: number) { - const [custody, verifications] = await Promise.all([getCustodyAddress(fid), getVerifiedAddresses(fid)]); - return { custody, verifications }; + const [custody, verifications] = await Promise.all([ + getCustodyAddress(fid), + getVerifiedAddresses(fid), + ]) + return { custody, verifications } } diff --git a/apps/relay/src/utils/getConfig.ts b/apps/relay/src/utils/getConfig.ts index a139a12..d369e6a 100644 --- a/apps/relay/src/utils/getConfig.ts +++ b/apps/relay/src/utils/getConfig.ts @@ -1,20 +1,36 @@ -import { createPublicClient, http } from "viem"; -import { optimism } from "viem/chains"; -import { z } from "zod"; -import { fromZodError } from "zod-validation-error"; +import { http, createPublicClient } from 'viem' +import { optimism } from 'viem/chains' +import { z } from 'zod' +import { fromZodError } from 'zod-validation-error' const schema = z.object({ corsOrigin: z.string(), redisUrl: z.string().url(), channelTtl: z.coerce.number(), port: z.number().optional().default(8000), - host: z.string().optional().default("localhost"), - baseUrl: z.string().url().optional().default("https://warpcast.com/~/sign-in-with-farcaster"), - hubUrl: z.string().url().optional().default("https://nemes.farcaster.xyz:2281"), - hubFallbackUrl: z.string().url().optional().default("https://hoyt.farcaster.xyz:2281"), - optimismRpcUrl: z.string().url().optional().default("https://mainnet.optimism.io"), + host: z.string().optional().default('localhost'), + baseUrl: z + .string() + .url() + .optional() + .default('https://warpcast.com/~/sign-in-with-farcaster'), + hubUrl: z + .string() + .url() + .optional() + .default('https://nemes.farcaster.xyz:2281'), + hubFallbackUrl: z + .string() + .url() + .optional() + .default('https://hoyt.farcaster.xyz:2281'), + optimismRpcUrl: z + .string() + .url() + .optional() + .default('https://mainnet.optimism.io'), authKey: z.string(), -}); +}) export function getConfig() { const parseResult = schema.safeParse({ @@ -28,17 +44,17 @@ export function getConfig() { hubFallbackUrl: process.env.HUB_FALLBACK_URL, optimismRpcUrl: process.env.OPTIMISM_RPC_URL, authKey: process.env.AUTH_KEY, - }); + }) if (!parseResult.success) { - throw fromZodError(parseResult.error); + throw fromZodError(parseResult.error) } - const env = parseResult.data; + const env = parseResult.data const publicClient = createPublicClient({ chain: optimism, transport: http(env.optimismRpcUrl), - }); + }) - return { ...env, publicClient }; + return { ...env, publicClient } } diff --git a/apps/relay/src/utils/getCustodyAddress.ts b/apps/relay/src/utils/getCustodyAddress.ts index ac5bc75..96da21e 100644 --- a/apps/relay/src/utils/getCustodyAddress.ts +++ b/apps/relay/src/utils/getCustodyAddress.ts @@ -1,13 +1,13 @@ -import { getConfig } from "./getConfig.js"; -import { ID_REGISTRY_ADDRESS, idRegistryABI } from "@farcaster/core"; +import { ID_REGISTRY_ADDRESS, idRegistryABI } from '@farcaster/core' +import { getConfig } from './getConfig.js' -const { publicClient } = getConfig(); +const { publicClient } = getConfig() export async function getCustodyAddress(fid: number) { return publicClient.readContract({ address: ID_REGISTRY_ADDRESS, abi: idRegistryABI, - functionName: "custodyOf", + functionName: 'custodyOf', args: [BigInt(fid ?? 0)], - }); + }) } diff --git a/apps/relay/src/utils/getVerifiedAddresses.ts b/apps/relay/src/utils/getVerifiedAddresses.ts index a60b039..c482498 100644 --- a/apps/relay/src/utils/getVerifiedAddresses.ts +++ b/apps/relay/src/utils/getVerifiedAddresses.ts @@ -1,12 +1,12 @@ -import { type VerificationsAPIResponse } from "../types/verifications.js"; -import { getConfig } from "./getConfig.js"; -import { fetch, Agent } from "undici"; +import { Agent, fetch } from 'undici' +import type { VerificationsAPIResponse } from '../types/verifications.js' +import { getConfig } from './getConfig.js' -const { hubUrl, hubFallbackUrl } = getConfig(); +const { hubUrl, hubFallbackUrl } = getConfig() export async function getVerifiedAddresses(fid: number) { - const mainUrl = `${hubUrl}/v1/verificationsByFid?fid=${fid}`; - const fallbackUrl = `${hubFallbackUrl}/v1/verificationsByFid?fid=${fid}`; + const mainUrl = `${hubUrl}/v1/verificationsByFid?fid=${fid}` + const fallbackUrl = `${hubFallbackUrl}/v1/verificationsByFid?fid=${fid}` const verifications = await fetch(mainUrl, { dispatcher: new Agent({ connectTimeout: 1500 }), }) @@ -15,10 +15,13 @@ export async function getVerifiedAddresses(fid: number) { .catch(async () => { return fetch(fallbackUrl, { dispatcher: new Agent({ connectTimeout: 3500 }), - }).then(async (r) => (await r.json()) as VerificationsAPIResponse); - }); + }).then(async (r) => (await r.json()) as VerificationsAPIResponse) + }) return verifications.messages.map((message) => { - return message.data?.verificationAddAddressBody?.address || message.data?.verificationAddEthAddressBody?.address; - }); + return ( + message.data?.verificationAddAddressBody?.address || + message.data?.verificationAddEthAddressBody?.address + ) + }) } diff --git a/biome.json b/biome.json index 9fffeb7..555173b 100644 --- a/biome.json +++ b/biome.json @@ -1,26 +1,52 @@ { - "$schema": "./node_modules/@biomejs/biome/configuration_schema.json", + "$schema": "https://biomejs.dev/schemas/1.8.0/schema.json", + "files": { + "ignore": [ + "coverage", + ".next", + "node_modules", + "tsconfig.json", + "tsconfig.*.json", + "**/dist/**" + ] + }, "organizeImports": { - "enabled": false + "enabled": true }, "formatter": { "enabled": true, - "indentSize": 2, "indentStyle": "space", - "lineWidth": 120, - "ignore": ["examples"] + "indentWidth": 2 }, "linter": { "enabled": true, "rules": { "recommended": true, + "a11y": { + "useAltText": "off", + "noSvgWithoutTitle": "off", + "useKeyWithClickEvents": "off" + }, "complexity": { - "useLiteralKeys": "off" + "noUselessFragments": "off", + "noBannedTypes": "off", + "noForEach": "off" }, "suspicious": { - "noExplicitAny": "off" + "noExplicitAny": "off", + "noRedeclare": "off", + "noArrayIndexKey": "off" + }, + "style": { + "noNonNullAssertion": "off" } - }, - "ignore": ["examples"] + } + }, + "javascript": { + "formatter": { + "quoteStyle": "single", + "trailingCommas": "all", + "semicolons": "asNeeded" + } } } diff --git a/examples/authkit-expo-demo/App.js b/examples/authkit-expo-demo/App.js index c665ccc..e93da2c 100644 --- a/examples/authkit-expo-demo/App.js +++ b/examples/authkit-expo-demo/App.js @@ -1,10 +1,10 @@ // polyfill TextEncoder -import 'fastestsmallesttextencoderdecoder'; +import 'fastestsmallesttextencoderdecoder' -import { StatusBar } from 'expo-status-bar'; -import { StyleSheet, Text, View, Button, Linking } from 'react-native'; -import { AuthKitProvider, useSignIn } from '@fc-auth/react'; -import { useEffect, useCallback } from 'react'; +import { AuthKitProvider, useSignIn } from '@fc-auth/react' +import { StatusBar } from 'expo-status-bar' +import { useCallback, useEffect } from 'react' +import { Button, Linking, StyleSheet, Text, View } from 'react-native' const config = { rpcUrl: 'https://mainnet.optimism.io', @@ -12,7 +12,7 @@ const config = { siweUri: 'https://example.com/login', relay: 'http://localhost:8000', redirectUrl: 'exp://192.168.0.168:8081', -}; +} export default function App() { return ( @@ -20,7 +20,7 @@ export default function App() { - ); + ) } function Content() { @@ -33,42 +33,45 @@ function Content() { reconnect, channelToken, data, - validSignature - } = useSignIn(); + validSignature, + } = useSignIn() const onClick = useCallback(() => { if (isError) { - reconnect(); + reconnect() } - signIn(); + signIn() if (url) { - Linking.openURL(url); + Linking.openURL(url) } - }, [isError, reconnect, signIn, url]); + }, [isError, reconnect, signIn, url]) useEffect(() => { if (!channelToken) { - connect(); + connect() } - }, [channelToken, connect]); + }, [channelToken, connect]) - const authenticated = isSuccess && validSignature; + const authenticated = isSuccess && validSignature return ( - {authenticated ? + {authenticated ? ( Signed in! {data?.username} - : + + ) : ( - + - } + )} - ); + ) } const styles = StyleSheet.create({ @@ -78,4 +81,4 @@ const styles = StyleSheet.create({ alignItems: 'center', justifyContent: 'center', }, -}); +}) diff --git a/examples/authkit-expo-demo/app.json b/examples/authkit-expo-demo/app.json index c1d4e5f..c8109e3 100644 --- a/examples/authkit-expo-demo/app.json +++ b/examples/authkit-expo-demo/app.json @@ -11,9 +11,7 @@ "resizeMode": "contain", "backgroundColor": "#ffffff" }, - "assetBundlePatterns": [ - "**/*" - ], + "assetBundlePatterns": ["**/*"], "ios": { "supportsTablet": true }, diff --git a/examples/authkit-expo-demo/babel.config.js b/examples/authkit-expo-demo/babel.config.js index 2900afe..9f057ea 100644 --- a/examples/authkit-expo-demo/babel.config.js +++ b/examples/authkit-expo-demo/babel.config.js @@ -1,6 +1,6 @@ -module.exports = function(api) { - api.cache(true); +module.exports = (api) => { + api.cache(true) return { presets: ['babel-preset-expo'], - }; -}; + } +} diff --git a/examples/authkit-expo-demo/index.js b/examples/authkit-expo-demo/index.js index 1d6e981..a8644b1 100644 --- a/examples/authkit-expo-demo/index.js +++ b/examples/authkit-expo-demo/index.js @@ -1,8 +1,8 @@ -import { registerRootComponent } from 'expo'; +import { registerRootComponent } from 'expo' -import App from './App'; +import App from './App' // registerRootComponent calls AppRegistry.registerComponent('main', () => App); // It also ensures that whether you load the app in Expo Go or in a native build, // the environment is set up appropriately -registerRootComponent(App); +registerRootComponent(App) diff --git a/examples/authkit-expo-demo/metro.config.js b/examples/authkit-expo-demo/metro.config.js index e50eb26..31ef081 100644 --- a/examples/authkit-expo-demo/metro.config.js +++ b/examples/authkit-expo-demo/metro.config.js @@ -1,19 +1,19 @@ -const { getDefaultConfig } = require('expo/metro-config'); -const path = require('path'); +const { getDefaultConfig } = require('expo/metro-config') +const path = require('node:path') // Find the project and workspace directories -const projectRoot = __dirname; +const projectRoot = __dirname // This can be replaced with `find-yarn-workspace-root` -const monorepoRoot = path.resolve(projectRoot, '../..'); +const monorepoRoot = path.resolve(projectRoot, '../..') -const config = getDefaultConfig(projectRoot); +const config = getDefaultConfig(projectRoot) // 1. Watch all files within the monorepo -config.watchFolders = [monorepoRoot]; +config.watchFolders = [monorepoRoot] // 2. Let Metro know where to resolve packages and in what order config.resolver.nodeModulesPaths = [ path.resolve(projectRoot, 'node_modules'), path.resolve(monorepoRoot, 'node_modules'), -]; +] -module.exports = config; +module.exports = config diff --git a/examples/frontend-only/.eslintrc.cjs b/examples/frontend-only/.eslintrc.cjs index 78174f6..d6c9537 100644 --- a/examples/frontend-only/.eslintrc.cjs +++ b/examples/frontend-only/.eslintrc.cjs @@ -1,11 +1,18 @@ module.exports = { root: true, env: { browser: true, es2020: true }, - extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:react-hooks/recommended"], - ignorePatterns: ["dist", ".eslintrc.cjs"], - parser: "@typescript-eslint/parser", - plugins: ["react-refresh"], + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:react-hooks/recommended', + ], + ignorePatterns: ['dist', '.eslintrc.cjs'], + parser: '@typescript-eslint/parser', + plugins: ['react-refresh'], rules: { - "react-refresh/only-export-components": ["warn", { allowConstantExport: true }], + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], }, -}; +} diff --git a/examples/frontend-only/src/App.tsx b/examples/frontend-only/src/App.tsx index f98995f..15d397c 100644 --- a/examples/frontend-only/src/App.tsx +++ b/examples/frontend-only/src/App.tsx @@ -1,36 +1,45 @@ -import { JsonRpcProvider } from "ethers"; -import "@fc-auth/react/styles.css"; -import { AuthKitProvider, createConfig, SignInButton, useProfile } from "@fc-auth/react"; -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { + AuthKitProvider, + SignInButton, + createConfig, + useProfile, +} from '@fc-auth/react' +import '@fc-auth/react/styles.css' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { JsonRpcProvider } from 'ethers' -const queryClient = new QueryClient(); +const queryClient = new QueryClient() const config = createConfig({ // For a production app, replace this with an Optimism Mainnet // RPC URL from a provider like Alchemy or Infura. - relay: "https://relay.farcaster.xyz", - rpcUrl: "https://mainnet.optimism.io", - domain: "example.com", - siweUri: "https://example.com/login", - provider: new JsonRpcProvider(undefined, 10) -}); + relay: 'https://relay.farcaster.xyz', + rpcUrl: 'https://mainnet.optimism.io', + domain: 'example.com', + siweUri: 'https://example.com/login', + provider: new JsonRpcProvider(undefined, 10), +}) function App() { return (
-
+
-
+

@farcaster/auth-kit + Vite

- This example app shows how to use{" "} - + This example app shows how to use{' '} + Farcaster AuthKit - {" "} - and{" "} + {' '} + and{' '} Vite @@ -41,14 +50,14 @@ function App() {

Run this demo:

git clone https://github.com/farcasterxyz/auth-monorepo.git && @@ -64,11 +73,11 @@ function App() {
- ); + ) } function Profile() { - const profile = useProfile(); + const profile = useProfile() return ( <> @@ -82,10 +91,13 @@ function Profile() {

) : ( -

Click the "Sign in with Farcaster" button above, then scan the QR code to sign in.

+

+ Click the "Sign in with Farcaster" button above, then scan the QR code + to sign in. +

)} - ); + ) } -export default App; +export default App diff --git a/examples/frontend-only/src/main.tsx b/examples/frontend-only/src/main.tsx index 3646e29..cb180fa 100644 --- a/examples/frontend-only/src/main.tsx +++ b/examples/frontend-only/src/main.tsx @@ -1,12 +1,12 @@ -import React from "react"; -import ReactDOM from "react-dom/client"; -import App from "./App.tsx"; -const root = document.getElementById("root"); +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App.tsx' +const root = document.getElementById('root') if (root) { ReactDOM.createRoot(root).render( , - ); + ) } diff --git a/examples/frontend-only/turbo.json b/examples/frontend-only/turbo.json index 0d8e5c5..9877c79 100644 --- a/examples/frontend-only/turbo.json +++ b/examples/frontend-only/turbo.json @@ -4,7 +4,12 @@ "pipeline": { "build": { "dependsOn": ["@fc-auth/core#build"], - "inputs": ["src/**", "vite.config.ts", "tsconfig.json", "tsconfig.node.json"], + "inputs": [ + "src/**", + "vite.config.ts", + "tsconfig.json", + "tsconfig.node.json" + ], "outputs": ["dist/**"] } } diff --git a/examples/frontend-only/vite.config.ts b/examples/frontend-only/vite.config.ts index 9eec838..f37e68d 100644 --- a/examples/frontend-only/vite.config.ts +++ b/examples/frontend-only/vite.config.ts @@ -1,8 +1,8 @@ -import { defineConfig } from "vite"; -import react from "@vitejs/plugin-react"; -import { nodePolyfills } from "vite-plugin-node-polyfills"; +import react from '@vitejs/plugin-react' +import { defineConfig } from 'vite' +import { nodePolyfills } from 'vite-plugin-node-polyfills' // https://vitejs.dev/config/ export default defineConfig({ - plugins: [react(), nodePolyfills({ include: ["buffer"] })], -}); + plugins: [react(), nodePolyfills({ include: ['buffer'] })], +}) diff --git a/examples/with-next-auth/next.config.js b/examples/with-next-auth/next.config.js index 91ef62f..a843cbe 100644 --- a/examples/with-next-auth/next.config.js +++ b/examples/with-next-auth/next.config.js @@ -1,6 +1,6 @@ /** @type {import('next').NextConfig} */ const nextConfig = { reactStrictMode: true, -}; +} -module.exports = nextConfig; +module.exports = nextConfig diff --git a/examples/with-next-auth/pages/_app.tsx b/examples/with-next-auth/pages/_app.tsx index eed9c9b..9a1a30b 100644 --- a/examples/with-next-auth/pages/_app.tsx +++ b/examples/with-next-auth/pages/_app.tsx @@ -1,10 +1,13 @@ -import { SessionProvider } from "next-auth/react"; -import type { AppProps } from "next/app"; +import { SessionProvider } from 'next-auth/react' +import type { AppProps } from 'next/app' -export default function App({ Component, pageProps: { session, ...pageProps } }: AppProps) { +export default function App({ + Component, + pageProps: { session, ...pageProps }, +}: AppProps) { return ( - ); + ) } diff --git a/examples/with-next-auth/pages/_document.tsx b/examples/with-next-auth/pages/_document.tsx index b2fff8b..bd0949d 100644 --- a/examples/with-next-auth/pages/_document.tsx +++ b/examples/with-next-auth/pages/_document.tsx @@ -1,4 +1,4 @@ -import { Html, Head, Main, NextScript } from "next/document"; +import { Head, Html, Main, NextScript } from 'next/document' export default function Document() { return ( @@ -9,5 +9,5 @@ export default function Document() { - ); + ) } diff --git a/examples/with-next-auth/pages/api/auth/[...nextauth].ts b/examples/with-next-auth/pages/api/auth/[...nextauth].ts index 5001ec8..2ed0945 100644 --- a/examples/with-next-auth/pages/api/auth/[...nextauth].ts +++ b/examples/with-next-auth/pages/api/auth/[...nextauth].ts @@ -1,66 +1,66 @@ -import NextAuth from "next-auth"; -import CredentialsProvider from "next-auth/providers/credentials"; -import { createAppClient, viemConnector } from "@fc-auth/core"; -import { NextApiRequest, NextApiResponse } from "next"; -import { JsonRpcProvider } from "ethers"; +import { createAppClient, viemConnector } from '@fc-auth/core' +import { JsonRpcProvider } from 'ethers' +import type { NextApiRequest, NextApiResponse } from 'next' +import NextAuth from 'next-auth' +import CredentialsProvider from 'next-auth/providers/credentials' export default (req: NextApiRequest, res: NextApiResponse) => NextAuth(req, res, { providers: [ CredentialsProvider({ - name: "Sign in with Farcaster", + name: 'Sign in with Farcaster', credentials: { message: { - label: "Message", - type: "text", - placeholder: "0x0", + label: 'Message', + type: 'text', + placeholder: '0x0', }, signature: { - label: "Signature", - type: "text", - placeholder: "0x0", + label: 'Signature', + type: 'text', + placeholder: '0x0', }, // In a production app with a server, these should be fetched from // your Farcaster data indexer rather than have them accepted as part // of credentials. name: { - label: "Name", - type: "text", - placeholder: "0x0", + label: 'Name', + type: 'text', + placeholder: '0x0', }, pfp: { - label: "Pfp", - type: "text", - placeholder: "0x0", + label: 'Pfp', + type: 'text', + placeholder: '0x0', }, }, async authorize(credentials) { const { body: { csrfToken }, - } = req; + } = req const appClient = createAppClient({ ethereum: viemConnector(), - }); + }) const verifyResponse = await appClient.verifySiweMessage({ message: credentials?.message as string, signature: credentials?.signature as `0x${string}`, - domain: "example.com", + domain: 'example.com', nonce: csrfToken, - }); - const { success, fid } = verifyResponse; + }) + const { success, fid } = verifyResponse if (!success) { - return null; + return null } return { id: fid.toString(), name: credentials?.name, image: credentials?.pfp, - }; + } }, }), ], - }); + }) diff --git a/examples/with-next-auth/pages/index.tsx b/examples/with-next-auth/pages/index.tsx index fd3bd50..69ffa4b 100644 --- a/examples/with-next-auth/pages/index.tsx +++ b/examples/with-next-auth/pages/index.tsx @@ -1,26 +1,31 @@ -import "@fc-auth/react/styles.css"; +import '@fc-auth/react/styles.css' -import Head from "next/head"; -import { useSession, signIn, signOut, getCsrfToken } from "next-auth/react"; -import { SignInButton, AuthKitProvider, createConfig, SignInReturnType } from "@fc-auth/react"; -import { useCallback, useState } from "react"; -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { + AuthKitProvider, + SignInButton, + type SignInReturnType, + createConfig, +} from '@fc-auth/react' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { getCsrfToken, signIn, signOut, useSession } from 'next-auth/react' +import Head from 'next/head' +import { useCallback, useState } from 'react' const config = createConfig({ - relay: "https://relay.farcaster.xyz", - rpcUrl: "https://mainnet.optimism.io", + relay: 'https://relay.farcaster.xyz', + rpcUrl: 'https://mainnet.optimism.io', domain: 'example.com', - siweUri: 'https://example.com/login' -}); + siweUri: 'https://example.com/login', +}) -const queryClient = new QueryClient(); +const queryClient = new QueryClient() export default function Home() { return ( <> Farcaster AuthKit + NextAuth Demo -
+
@@ -28,31 +33,31 @@ export default function Home() {
- ); + ) } function Content() { - const [error, setError] = useState(false); + const [error, setError] = useState(false) const getNonce = useCallback(async () => { - const nonce = await getCsrfToken(); - if (!nonce) throw new Error("Unable to generate nonce"); - return nonce; - }, []); + const nonce = await getCsrfToken() + if (!nonce) throw new Error('Unable to generate nonce') + return nonce + }, []) const handleSuccess = useCallback((res: SignInReturnType) => { - signIn("credentials", { + signIn('credentials', { message: res.message, signature: res.signature, name: res.username, pfp: res.pfpUrl, redirect: false, - }); - }, [signIn]); + }) + }, []) return (
-
+
{error &&
Unable to sign in at this time.
}
-
+

@farcaster/auth-kit + NextAuth

- This example app shows how to use{" "} - + This example app shows how to use{' '} + Farcaster AuthKit - {" "} - and{" "} + {' '} + and{' '} NextAuth.js @@ -79,14 +88,14 @@ function Content() {

Run this demo:

git clone https://github.com/farcasterxyz/auth-monorepo.git && @@ -100,22 +109,29 @@ function Content() {
- ); + ) } function Profile() { - const { data: session } = useSession(); + const { data: session } = useSession() return session ? ( -
+

Signed in as {session.user?.name}

-

) : ( -

Click the "Sign in with Farcaster" button above, then scan the QR code to sign in.

- ); +

+ Click the "Sign in with Farcaster" button above, then scan the + QR code to sign in. +

+ ) } diff --git a/examples/with-next-auth/turbo.json b/examples/with-next-auth/turbo.json index 86fabdb..ab42178 100644 --- a/examples/with-next-auth/turbo.json +++ b/examples/with-next-auth/turbo.json @@ -4,7 +4,13 @@ "pipeline": { "build": { "dependsOn": ["@fc-auth/core#build"], - "inputs": ["src/**", "vite.config.ts", "tsconfig.json", "tsconfig.node.json", "next.config.js"], + "inputs": [ + "src/**", + "vite.config.ts", + "tsconfig.json", + "tsconfig.node.json", + "next.config.js" + ], "outputs": ["dist/**"] } } diff --git a/package.json b/package.json index 803ac57..4425e30 100644 --- a/package.json +++ b/package.json @@ -12,9 +12,9 @@ "lint": "biome check .", "preconstruct": "node --import tsx .scripts/preconstruct.ts", "prepare": "husky install", - "version-packages": "changeset version", - "release-packages": "pnpm build && changeset publish", - "release-all": "pnpm build && changeset publish && changeset tag" + "changeset": "changeset", + "changeset:release": "pnpm build && changeset publish", + "changeset:version": "changeset version && pnpm install --lockfile-only" }, "engines": { "npm": ">=8.0.0", @@ -24,7 +24,7 @@ "*.ts": ["biome check"] }, "devDependencies": { - "@biomejs/biome": "~1.1.2", + "@biomejs/biome": "~1.8.0", "@changesets/changelog-github": "0.4.6", "@changesets/cli": "2.25.2", "@swc/cli": "^0.1.65", @@ -32,6 +32,7 @@ "@swc/jest": "^0.2.36", "@types/jest": "^29.5.12", "@types/node": "^20.12.12", + "fast-glob": "^3.3.2", "husky": "^8.0.3", "jest": "^29.7.0", "lint-staged": "^15.2.2", diff --git a/packages/core/jest.config.ts b/packages/core/jest.config.ts index 8360503..e4c2a77 100644 --- a/packages/core/jest.config.ts +++ b/packages/core/jest.config.ts @@ -1,21 +1,21 @@ -import type { Config } from "jest"; +import type { Config } from 'jest' const jestConfig: Config = { - testEnvironment: "node", + testEnvironment: 'node', moduleNameMapper: { - "^~/(.*)$": "/src/$1", - "^(.+)_generated.js$": "$1_generated", // Support flatc generated files + '^~/(.*)$': '/src/$1', + '^(.+)_generated.js$': '$1_generated', // Support flatc generated files }, - coveragePathIgnorePatterns: ["/build/", "/node_modules/"], - testPathIgnorePatterns: ["/build", "/node_modules"], - extensionsToTreatAsEsm: [".ts"], + coveragePathIgnorePatterns: ['/build/', '/node_modules/'], + testPathIgnorePatterns: ['/build', '/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", + '^.+\\.(t|j)sx?$': '@swc/jest', }, -}; +} -export default jestConfig; +export default jestConfig diff --git a/packages/core/package.json b/packages/core/package.json index b3876fb..536c62a 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -64,21 +64,11 @@ }, "typesVersions": { "*": { - "actions": [ - "./dist/types/exports/actions.d.ts" - ], - "clients": [ - "./dist/types/exports/clients.d.ts" - ], - "contracts": [ - "./dist/types/exports/contracts.d.ts" - ], - "errors": [ - "./dist/types/exports/errors.d.ts" - ], - "utils": [ - "./dist/types/exports/utils.d.ts" - ] + "actions": ["./dist/types/exports/actions.d.ts"], + "clients": ["./dist/types/exports/clients.d.ts"], + "contracts": ["./dist/types/exports/contracts.d.ts"], + "errors": ["./dist/types/exports/errors.d.ts"], + "utils": ["./dist/types/exports/utils.d.ts"] } }, "dependencies": { diff --git a/packages/core/scripts/console.ts b/packages/core/scripts/console.ts index c7dcacd..6b0b128 100644 --- a/packages/core/scripts/console.ts +++ b/packages/core/scripts/console.ts @@ -1,32 +1,40 @@ -import path from "path"; -import { createAppClient, createWalletClient, viemConnector } from "../src/index"; -import * as repl from "repl"; - -(async () => { - console.log("Loading console environment..."); +import path from 'node:path' +import * as repl from 'node:repl' +import { + createAppClient, + createWalletClient, + viemConnector, +} from '../src/index' +;(async () => { + console.log('Loading console environment...') const context = { createAppClient, createWalletClient, viemConnector, appClient: createAppClient({ - relay: "http://localhost:8000", + relay: 'http://localhost:8000', ethereum: viemConnector(), }), walletClient: createWalletClient({ - relay: "http://localhost:8000", + relay: 'http://localhost:8000', ethereum: viemConnector(), }), - }; + } - const replServer = repl.start({ prompt: "> ", breakEvalOnSigint: true }).on("exit", () => { - process.exit(0); - }); - replServer.setupHistory(path.resolve(__dirname, "..", "..", ".console-history"), (err) => { - if (err !== null) console.log(err); - }); + const replServer = repl + .start({ prompt: '> ', breakEvalOnSigint: true }) + .on('exit', () => { + process.exit(0) + }) + replServer.setupHistory( + path.resolve(__dirname, '..', '..', '.console-history'), + (err) => { + if (err !== null) console.log(err) + }, + ) for (const [key, value] of Object.entries(context)) { - replServer.context[key] = value; + replServer.context[key] = value } -})(); +})() diff --git a/packages/core/src/actions/app/channel.test.ts b/packages/core/src/actions/app/channel.test.ts index 51b6c4a..b675313 100644 --- a/packages/core/src/actions/app/channel.test.ts +++ b/packages/core/src/actions/app/channel.test.ts @@ -1,37 +1,42 @@ -import { createAppClient } from "../../clients/createAppClient.js"; -import { jest } from "@jest/globals"; -import { viemConnector } from "../../clients/ethereum/viemConnector.js"; -import { type ChannelReturnType } from "./channel.js"; +import { jest } from '@jest/globals' +import { createAppClient } from '../../clients/createAppClient.js' +import { viemConnector } from '../../clients/ethereum/viemConnector.js' +import type { ChannelReturnType } from './channel.js' -describe("channel", () => { +describe('channel', () => { const client = createAppClient({ ethereum: viemConnector(), - }); + }) afterEach(() => { - jest.restoreAllMocks(); - }); + jest.restoreAllMocks() + }) const channelResponseDataStub: ChannelReturnType = { - state: "pending", - nonce: "abcd1234", - url: "https://warpcast.com/~/sign-in-with-farcaster?nonce=abcd1234[...]", - }; + state: 'pending', + nonce: 'abcd1234', + url: 'https://warpcast.com/~/sign-in-with-farcaster?nonce=abcd1234[...]', + } - test("constructs request", async () => { - const spy = jest.spyOn(global, "fetch").mockResolvedValue(new Response(JSON.stringify(channelResponseDataStub))); + test('constructs request', async () => { + const spy = jest + .spyOn(global, 'fetch') + .mockResolvedValue(new Response(JSON.stringify(channelResponseDataStub))) const res = await client.channel({ - channelToken: "some-channel-token", - }); + channelToken: 'some-channel-token', + }) - expect(res).toEqual(channelResponseDataStub); - expect(spy).toHaveBeenCalledTimes(1); - expect(spy).toHaveBeenCalledWith("https://relay.farcaster.xyz/v1/channel/channel", { - headers: { - "Content-Type": "application/json", - Authorization: "Bearer some-channel-token", + expect(res).toEqual(channelResponseDataStub) + expect(spy).toHaveBeenCalledTimes(1) + expect(spy).toHaveBeenCalledWith( + 'https://relay.farcaster.xyz/v1/channel/channel', + { + headers: { + 'Content-Type': 'application/json', + Authorization: 'Bearer some-channel-token', + }, }, - }); - }); -}); + ) + }) +}) diff --git a/packages/core/src/actions/app/channel.ts b/packages/core/src/actions/app/channel.ts index 5f278e8..b3c4f73 100644 --- a/packages/core/src/actions/app/channel.ts +++ b/packages/core/src/actions/app/channel.ts @@ -1,17 +1,20 @@ -import { type Client } from "../../clients/createClient.js"; -import { get } from "../../clients/transports/http.js"; -import { type ChannelGetReturnType } from "@fc-auth/relay"; +import type { ChannelGetReturnType } from '@fc-auth/relay' +import type { Client } from '../../clients/createClient.js' +import { get } from '../../clients/transports/http.js' export type ChannelParameters = { - channelToken: string; -}; + channelToken: string +} -export type ChannelReturnType = ChannelGetReturnType; +export type ChannelReturnType = ChannelGetReturnType -const path = "channel/status"; +const path = 'channel/status' -export const channel = (client: Client, { channelToken }: ChannelParameters): Promise => { +export const channel = ( + client: Client, + { channelToken }: ChannelParameters, +): Promise => { return get(client, path, { channelToken: channelToken, - }); -}; + }) +} diff --git a/packages/core/src/actions/app/createChannel.test.ts b/packages/core/src/actions/app/createChannel.test.ts index d259f9d..3bf215d 100644 --- a/packages/core/src/actions/app/createChannel.test.ts +++ b/packages/core/src/actions/app/createChannel.test.ts @@ -1,67 +1,71 @@ -import { createAppClient } from "../../clients/createAppClient.js"; -import { jest } from "@jest/globals"; -import { viemConnector } from "../../clients/ethereum/viemConnector.js"; -import { AuthClientError } from "../../errors.js"; -import { type CreateChannelReturnType } from "./createChannel.js"; +import { jest } from '@jest/globals' +import { createAppClient } from '../../clients/createAppClient.js' +import { viemConnector } from '../../clients/ethereum/viemConnector.js' +import { AuthClientError } from '../../errors.js' +import type { CreateChannelReturnType } from './createChannel.js' -describe("createChannel", () => { +describe('createChannel', () => { const client = createAppClient({ ethereum: viemConnector(), - }); + }) afterEach(() => { - jest.restoreAllMocks(); - }); + jest.restoreAllMocks() + }) - const siweUri = "https://example.com/login"; - const domain = "example.com"; - const nonce = "abcd1234"; + const siweUri = 'https://example.com/login' + const domain = 'example.com' + const nonce = 'abcd1234' const createChannelResponseDataStub: CreateChannelReturnType = { - url: "https://some-url", - state: "pending", + url: 'https://some-url', + state: 'pending', nonce, - }; + } - test("constructs API request", async () => { + test('constructs API request', async () => { const spy = jest - .spyOn(global, "fetch") - .mockResolvedValue(new Response(JSON.stringify(createChannelResponseDataStub))); + .spyOn(global, 'fetch') + .mockResolvedValue( + new Response(JSON.stringify(createChannelResponseDataStub)), + ) const res = await client.createChannel({ siweUri, domain, nonce, - }); + }) - expect(res).toEqual(createChannelResponseDataStub); - expect(spy).toHaveBeenCalledTimes(1); - expect(spy).toHaveBeenCalledWith("https://relay.farcaster.xyz/v1/channel", { - method: "POST", + expect(res).toEqual(createChannelResponseDataStub) + expect(spy).toHaveBeenCalledTimes(1) + expect(spy).toHaveBeenCalledWith('https://relay.farcaster.xyz/v1/channel', { + method: 'POST', body: JSON.stringify({ siweUri, domain, nonce, }), headers: { - "Content-Type": "application/json", + 'Content-Type': 'application/json', }, - }); - }); + }) + }) - test("handles errors", async () => { - const spy = jest.spyOn(global, "fetch").mockRejectedValue(new Error("some error")); + test('handles errors', async () => { + const spy = jest + .spyOn(global, 'fetch') + .mockRejectedValue(new Error('some error')) try { await client.createChannel({ siweUri, domain, nonce, - }); - expect(true).toBe(false); + }) + expect(true).toBe(false) } catch (e) { - expect(spy).toHaveBeenCalledTimes(1); - expect(e).toEqual(new AuthClientError("unknown", "some error")); + expect(spy).toHaveBeenCalledTimes(1) + expect(e).toEqual(new AuthClientError('unknown', 'some error')) } - }); -}); + }) +}) diff --git a/packages/core/src/actions/app/createChannel.ts b/packages/core/src/actions/app/createChannel.ts index 0853839..35748bc 100644 --- a/packages/core/src/actions/app/createChannel.ts +++ b/packages/core/src/actions/app/createChannel.ts @@ -1,16 +1,23 @@ -import { type ChannelCreateParameters, type ChannelCreateReturnType } from "@fc-auth/relay"; -import { type Client } from "../../clients/createClient.js"; -import { post } from "../../clients/transports/http.js"; +import type { + ChannelCreateParameters, + ChannelCreateReturnType, +} from '@fc-auth/relay' +import type { Client } from '../../clients/createClient.js' +import { post } from '../../clients/transports/http.js' -export type CreateChannelParameters = ChannelCreateParameters; +export type CreateChannelParameters = ChannelCreateParameters -export type CreateChannelReturnType = ChannelCreateReturnType; +export type CreateChannelReturnType = ChannelCreateReturnType -const path = "channel"; +const path = 'channel' export const createChannel = ( client: Client, parameters: CreateChannelParameters, ): Promise => { - return post(client, path, parameters); -}; + return post( + client, + path, + parameters, + ) +} diff --git a/packages/core/src/actions/app/index.ts b/packages/core/src/actions/app/index.ts index 6c5eb76..56205a2 100644 --- a/packages/core/src/actions/app/index.ts +++ b/packages/core/src/actions/app/index.ts @@ -1,4 +1,4 @@ -export * from "./createChannel.js"; -export * from "./channel.js"; -export * from "./verifySiweMessage.js"; -export * from "./pollChannelTillCompleted.js"; +export * from './createChannel.js' +export * from './channel.js' +export * from './verifySiweMessage.js' +export * from './pollChannelTillCompleted.js' diff --git a/packages/core/src/actions/app/pollChannelTillCompleted.test.ts b/packages/core/src/actions/app/pollChannelTillCompleted.test.ts index b96775b..36c8b44 100644 --- a/packages/core/src/actions/app/pollChannelTillCompleted.test.ts +++ b/packages/core/src/actions/app/pollChannelTillCompleted.test.ts @@ -1,35 +1,35 @@ -import { createAppClient } from "../../clients/createAppClient.js"; -import { jest } from "@jest/globals"; -import { viemConnector } from "../../clients/ethereum/viemConnector.js"; +import { jest } from '@jest/globals' +import { createAppClient } from '../../clients/createAppClient.js' +import { viemConnector } from '../../clients/ethereum/viemConnector.js' -describe("channel", () => { +describe('channel', () => { const client = createAppClient({ ethereum: viemConnector(), - }); + }) afterEach(() => { - jest.restoreAllMocks(); - }); + jest.restoreAllMocks() + }) - test("polls for channel changes", async () => { - const pending1 = new Response(JSON.stringify({ state: "pending" }), { + test('polls for channel changes', async () => { + const pending1 = new Response(JSON.stringify({ state: 'pending' }), { status: 202, - }); - const pending2 = new Response(JSON.stringify({ state: "pending" }), { + }) + const pending2 = new Response(JSON.stringify({ state: 'pending' }), { status: 202, - }); - const completed = new Response(JSON.stringify({ state: "completed" })); + }) + const completed = new Response(JSON.stringify({ state: 'completed' })) const fetchSpy = jest - .spyOn(global, "fetch") + .spyOn(global, 'fetch') .mockResolvedValueOnce(pending1) .mockResolvedValueOnce(pending2) - .mockResolvedValueOnce(completed); + .mockResolvedValueOnce(completed) const res = await client.pollChannelTillCompleted({ - channelToken: "some-channel-token", - }); + channelToken: 'some-channel-token', + }) - expect(res).toEqual({ state: "completed" }); - expect(fetchSpy).toHaveBeenCalledTimes(3); - }); -}); + expect(res).toEqual({ state: 'completed' }) + expect(fetchSpy).toHaveBeenCalledTimes(3) + }) +}) diff --git a/packages/core/src/actions/app/pollChannelTillCompleted.ts b/packages/core/src/actions/app/pollChannelTillCompleted.ts index a4451f3..983f702 100644 --- a/packages/core/src/actions/app/pollChannelTillCompleted.ts +++ b/packages/core/src/actions/app/pollChannelTillCompleted.ts @@ -1,18 +1,18 @@ -import { AuthClientError } from "../../errors.js"; -import { type Client } from "../../clients/createClient.js"; -import { poll } from "../../clients/transports/http.js"; -import { type ChannelReturnType } from "./channel.js"; -import type { CompletedChannel } from "@fc-auth/relay"; +import type { CompletedChannel } from '@fc-auth/relay' +import type { Client } from '../../clients/createClient.js' +import { poll } from '../../clients/transports/http.js' +import { AuthClientError } from '../../errors.js' +import type { ChannelReturnType } from './channel.js' export type PollChannelTillCompletedParameters = { - channelToken: string; - timeout?: number; - interval?: number; -}; + channelToken: string + timeout?: number + interval?: number +} -export type PollChannelTillCompletedReturnType = CompletedChannel; +export type PollChannelTillCompletedReturnType = CompletedChannel -const path = "channel/status"; +const path = 'channel/status' export const pollChannelTillCompleted = async ( client: Client, @@ -27,12 +27,15 @@ export const pollChannelTillCompleted = async ( }, { channelToken: args.channelToken }, )) { - if (polledData.state === "completed") { + if (polledData.state === 'completed') { // type coercing to completed as if the response status is 200 // it is expected to have a completed status. - return polledData; + return polledData } } // This error should not be thrown, as the `poll` function will throw an error on timeout - throw new AuthClientError("unknown", "unexpected error in `pollChannelTillCompleted`"); -}; + throw new AuthClientError( + 'unknown', + 'unexpected error in `pollChannelTillCompleted`', + ) +} diff --git a/packages/core/src/actions/app/verifySiweMessage.test.ts b/packages/core/src/actions/app/verifySiweMessage.test.ts index 399fe27..863f353 100644 --- a/packages/core/src/actions/app/verifySiweMessage.test.ts +++ b/packages/core/src/actions/app/verifySiweMessage.test.ts @@ -1,69 +1,69 @@ -import { createAppClient } from "../../clients/createAppClient.js"; -import { viemConnector } from "../../clients/ethereum/viemConnector.js"; -import { privateKeyToAccount, generatePrivateKey } from "viem/accounts"; -import { AuthClientError } from "../../errors.js"; -import { createSiweMessage } from "../../utils/createSiweMessage.js"; -import { JsonRpcProvider } from "ethers"; +import { JsonRpcProvider } from 'ethers' +import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts' +import { createAppClient } from '../../clients/createAppClient.js' +import { viemConnector } from '../../clients/ethereum/viemConnector.js' +import { AuthClientError } from '../../errors.js' +import { createSiweMessage } from '../../utils/createSiweMessage.js' -describe("verifySiweMessage", () => { +describe('verifySiweMessage', () => { const client = createAppClient( { ethereum: viemConnector(), }, - new JsonRpcProvider("https://mainnet.optimism.io/", 10), - ); + new JsonRpcProvider('https://mainnet.optimism.io/', 10), + ) - const account = privateKeyToAccount(generatePrivateKey()); + const account = privateKeyToAccount(generatePrivateKey()) const siweParams = { - domain: "example.com", - uri: "https://example.com/login", - version: "1", - issuedAt: "2023-10-01T00:00:00.000Z", - nonce: "abcd1234", - }; - const { nonce, domain } = siweParams; + domain: 'example.com', + uri: 'https://example.com/login', + version: '1', + issuedAt: '2023-10-01T00:00:00.000Z', + nonce: 'abcd1234', + } + const { nonce, domain } = siweParams - test("verifies sign in message", async () => { + test('verifies sign in message', async () => { const siweMessage = createSiweMessage({ ...siweParams, address: account.address, fid: 1234, - }); + }) const signature = await account.signMessage({ message: siweMessage.toMessage(), - }); + }) - const errMsg = `Invalid resource: signer ${account.address} does not own fid 1234.`; - const err = new AuthClientError("unauthorized", errMsg); + const errMsg = `Invalid resource: signer ${account.address} does not own fid 1234.` + const err = new AuthClientError('unauthorized', errMsg) try { await client.verifySiweMessage({ nonce, domain, message: siweMessage.toMessage(), signature, - }); - expect(true).toBe(false); + }) + expect(true).toBe(false) } catch (e) { - expect(e).toStrictEqual(err); + expect(e).toStrictEqual(err) } - }); + }) - test("verifies 1271 sign in message", async () => { - const LGTM = "0xC89858205c6AdDAD842E1F58eD6c42452671885A"; + test('verifies 1271 sign in message', async () => { + const LGTM = '0xC89858205c6AdDAD842E1F58eD6c42452671885A' const siweMessage = createSiweMessage({ ...siweParams, address: LGTM, fid: 1234, - }); + }) const signature = await account.signMessage({ message: siweMessage.toMessage(), - }); + }) - const errMsg = `Invalid resource: signer ${LGTM} does not own fid 1234.`; - const err = new AuthClientError("unauthorized", errMsg); + const errMsg = `Invalid resource: signer ${LGTM} does not own fid 1234.` + const err = new AuthClientError('unauthorized', errMsg) try { await client.verifySiweMessage({ @@ -71,10 +71,10 @@ describe("verifySiweMessage", () => { domain, message: siweMessage, signature, - }); - expect(true).toBe(false); + }) + expect(true).toBe(false) } catch (e) { - expect(e).toStrictEqual(err); + expect(e).toStrictEqual(err) } - }); -}); + }) +}) diff --git a/packages/core/src/actions/app/verifySiweMessage.ts b/packages/core/src/actions/app/verifySiweMessage.ts index a639ccc..abc1736 100644 --- a/packages/core/src/actions/app/verifySiweMessage.ts +++ b/packages/core/src/actions/app/verifySiweMessage.ts @@ -1,15 +1,19 @@ -import { type Client } from "../../clients/createClient.js"; +import type { Provider } from 'ethers' +import type { Client } from '../../clients/createClient.js' +import type { Omit } from '../../types/utils.js' import { - type VerifySiweMessageWithVerifierReturnType, type VerifySiweMessageWithVerifierParameters, + type VerifySiweMessageWithVerifierReturnType, verifySiweMessageWithVerifier as util_verifySiweMessage, -} from "../../utils/verifySiweMessageWithVerifier.js"; -import type { Omit } from "../../types/utils.js"; -import type { Provider } from "ethers"; +} from '../../utils/verifySiweMessageWithVerifier.js' -export type VerifySiweMessageParameters = Omit; +export type VerifySiweMessageParameters = Omit< + VerifySiweMessageWithVerifierParameters, + 'verifier' +> -export type VerifySiweMessageReturnType = VerifySiweMessageWithVerifierReturnType; +export type VerifySiweMessageReturnType = + VerifySiweMessageWithVerifierReturnType export const verifySiweMessage = ( client: Client, @@ -25,5 +29,5 @@ export const verifySiweMessage = ( getFid: client.ethereum.getFid, provider: provider, }, - }); -}; + }) +} diff --git a/packages/core/src/actions/auth/authenticate.test.ts b/packages/core/src/actions/auth/authenticate.test.ts index 98cc08f..fef816c 100644 --- a/packages/core/src/actions/auth/authenticate.test.ts +++ b/packages/core/src/actions/auth/authenticate.test.ts @@ -1,30 +1,31 @@ -import { createWalletClient } from "../../clients/createWalletClient.js"; -import { jest } from "@jest/globals"; -import { viemConnector } from "../../clients/ethereum/viemConnector.js"; -import { type AuthenticateReturnType } from "./authenticate.js"; +import { jest } from '@jest/globals' +import { createWalletClient } from '../../clients/createWalletClient.js' +import { viemConnector } from '../../clients/ethereum/viemConnector.js' +import type { AuthenticateReturnType } from './authenticate.js' -describe("authenticate", () => { +describe('authenticate', () => { const client = createWalletClient({ - relay: "https://relay.farcaster.xyz", + relay: 'https://relay.farcaster.xyz', ethereum: viemConnector(), - }); + }) afterEach(() => { - jest.restoreAllMocks(); - }); + 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 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: AuthenticateReturnType = { - status: "completed", - nonce: "abcd1234", - url: "https://warpcast.com/~/sign-in-with-farcaster?nonce=abcd1234[...]", + status: 'completed', + nonce: 'abcd1234', + url: 'https://warpcast.com/~/sign-in-with-farcaster?nonce=abcd1234[...]', message, signature, fid, @@ -33,15 +34,17 @@ describe("authenticate", () => { displayName, pfpUrl, verifications: [], - custody: "0x0000000000000000000000000000000000000000", - }; + custody: '0x0000000000000000000000000000000000000000', + } - test("constructs API request", async () => { - const spy = jest.spyOn(global, "fetch").mockResolvedValue(new Response(JSON.stringify(statusResponseDataStub))); + test('constructs API request', async () => { + const spy = jest + .spyOn(global, 'fetch') + .mockResolvedValue(new Response(JSON.stringify(statusResponseDataStub))) const res = await client.authenticate({ - authKey: "some-auth-key", - channelToken: "some-channel-token", + authKey: 'some-auth-key', + channelToken: 'some-channel-token', message, signature, fid, @@ -49,26 +52,29 @@ describe("authenticate", () => { bio, displayName, pfpUrl, - }); + }) - expect(res).toEqual(statusResponseDataStub); - expect(spy).toHaveBeenCalledTimes(1); - expect(spy).toHaveBeenCalledWith("https://relay.farcaster.xyz/v1/channel/authenticate", { - method: "POST", - body: JSON.stringify({ - message, - signature, - fid, - username, - bio, - displayName, - pfpUrl, - }), - headers: { - "Content-Type": "application/json", - Authorization: "Bearer some-channel-token", - "X-Farcaster-Auth-Relay-Key": "some-auth-key", + expect(res).toEqual(statusResponseDataStub) + expect(spy).toHaveBeenCalledTimes(1) + expect(spy).toHaveBeenCalledWith( + 'https://relay.farcaster.xyz/v1/channel/authenticate', + { + method: 'POST', + body: JSON.stringify({ + message, + signature, + fid, + username, + bio, + displayName, + pfpUrl, + }), + headers: { + 'Content-Type': 'application/json', + Authorization: 'Bearer some-channel-token', + 'X-Farcaster-Auth-Relay-Key': 'some-auth-key', + }, }, - }); - }); -}); + ) + }) +}) diff --git a/packages/core/src/actions/auth/authenticate.ts b/packages/core/src/actions/auth/authenticate.ts index 3462157..6d7c00d 100644 --- a/packages/core/src/actions/auth/authenticate.ts +++ b/packages/core/src/actions/auth/authenticate.ts @@ -1,21 +1,30 @@ -import { type ChannelAuthenticateParameters, type ChannelAuthenticateReturnType } from "@fc-auth/relay"; -import { post } from "../../clients/transports/http.js"; -import { type Client } from "../../clients/createClient.js"; +import type { + ChannelAuthenticateParameters, + ChannelAuthenticateReturnType, +} from '@fc-auth/relay' +import type { Client } from '../../clients/createClient.js' +import { post } from '../../clients/transports/http.js' -export type AuthenticateParameters = ChannelAuthenticateParameters & { channelToken: string; authKey: string }; +export type AuthenticateParameters = ChannelAuthenticateParameters & { + channelToken: string + authKey: string +} -export type AuthenticateReturnType = ChannelAuthenticateReturnType; +export type AuthenticateReturnType = ChannelAuthenticateReturnType -const path = "channel/authenticate"; +const path = 'channel/authenticate' export const authenticate = async ( client: Client, { channelToken, authKey, ...request }: AuthenticateParameters, ): Promise => { - return post, AuthenticateReturnType>(client, path, request, { + return post< + Omit, + AuthenticateReturnType + >(client, path, request, { channelToken, headers: { - "X-Farcaster-Auth-Relay-Key": authKey, + 'X-Farcaster-Auth-Relay-Key': authKey, }, - }); -}; + }) +} diff --git a/packages/core/src/clients/createAppClient.test.ts b/packages/core/src/clients/createAppClient.test.ts index da5b089..272e0d6 100644 --- a/packages/core/src/clients/createAppClient.test.ts +++ b/packages/core/src/clients/createAppClient.test.ts @@ -1,38 +1,38 @@ -import { createAppClient, type AppClient } from "./createAppClient.js"; -import { viemConnector } from "./ethereum/viemConnector.js"; +import { type AppClient, createAppClient } from './createAppClient.js' +import { viemConnector } from './ethereum/viemConnector.js' -describe("createAppClient", () => { +describe('createAppClient', () => { const config = { ethereum: viemConnector(), - }; + } - let appClient: AppClient; + let appClient: AppClient beforeEach(() => { - appClient = createAppClient(config); - }); + appClient = createAppClient(config) + }) - test("adds version to config", () => { + test('adds version to config', () => { expect(appClient.config).toEqual({ - relay: "https://relay.farcaster.xyz", - version: "v1", - }); - }); + relay: 'https://relay.farcaster.xyz', + version: 'v1', + }) + }) - test("overrides version", () => { + test('overrides version', () => { appClient = createAppClient({ ...config, - version: "v2", - }); + version: 'v2', + }) expect(appClient.config).toEqual({ - relay: "https://relay.farcaster.xyz", - version: "v2", - }); - }); + relay: 'https://relay.farcaster.xyz', + version: 'v2', + }) + }) - test("includes app actions", () => { - expect(appClient.createChannel).toBeDefined(); - expect(appClient.status).toBeDefined(); - }); -}); + test('includes app actions', () => { + expect(appClient.createChannel).toBeDefined() + expect(appClient.status).toBeDefined() + }) +}) diff --git a/packages/core/src/clients/createAppClient.ts b/packages/core/src/clients/createAppClient.ts index b6bf3fc..66f55cd 100644 --- a/packages/core/src/clients/createAppClient.ts +++ b/packages/core/src/clients/createAppClient.ts @@ -1,36 +1,56 @@ +import type { Provider } from 'ethers' +import { + type ChannelParameters, + type ChannelReturnType, + channel, +} from '../actions/app/channel.js' import { - createChannel, type CreateChannelParameters, type CreateChannelReturnType, -} from "../actions/app/createChannel.js"; -import { channel, type ChannelParameters, type ChannelReturnType } from "../actions/app/channel.js"; + createChannel, +} from '../actions/app/createChannel.js' import { - pollChannelTillCompleted, type PollChannelTillCompletedParameters, type PollChannelTillCompletedReturnType, -} from "../actions/app/pollChannelTillCompleted.js"; + pollChannelTillCompleted, +} from '../actions/app/pollChannelTillCompleted.js' import { - verifySiweMessage, type VerifySiweMessageParameters, type VerifySiweMessageReturnType, -} from "../actions/app/verifySiweMessage.js"; -import { type Client, type CreateClientParameters, createClient } from "./createClient.js"; -import { type Provider } from "ethers"; + verifySiweMessage, +} from '../actions/app/verifySiweMessage.js' +import { + type Client, + type CreateClientParameters, + createClient, +} from './createClient.js' export interface AppClient extends Client { - createChannel: (args: CreateChannelParameters) => Promise; - channel: (args: ChannelParameters) => Promise; - pollChannelTillCompleted: (args: PollChannelTillCompletedParameters) => Promise; - verifySiweMessage: (args: VerifySiweMessageParameters) => Promise; + createChannel: ( + args: CreateChannelParameters, + ) => Promise + channel: (args: ChannelParameters) => Promise + pollChannelTillCompleted: ( + args: PollChannelTillCompletedParameters, + ) => Promise + verifySiweMessage: ( + args: VerifySiweMessageParameters, + ) => Promise } -export const createAppClient = (config: CreateClientParameters, provider?: Provider): AppClient => { - const client = createClient(config); +export const createAppClient = ( + config: CreateClientParameters, + provider?: Provider, +): AppClient => { + const client = createClient(config) return { ...client, - createChannel: (args: CreateChannelParameters) => createChannel(client, args), + createChannel: (args: CreateChannelParameters) => + createChannel(client, args), channel: (args: ChannelParameters) => channel(client, args), - pollChannelTillCompleted: (args: PollChannelTillCompletedParameters) => pollChannelTillCompleted(client, args), - verifySiweMessage: (args: VerifySiweMessageParameters) => verifySiweMessage(client, args, provider), - }; -}; + pollChannelTillCompleted: (args: PollChannelTillCompletedParameters) => + pollChannelTillCompleted(client, args), + verifySiweMessage: (args: VerifySiweMessageParameters) => + verifySiweMessage(client, args, provider), + } +} diff --git a/packages/core/src/clients/createClient.test.ts b/packages/core/src/clients/createClient.test.ts index eae6633..3691b69 100644 --- a/packages/core/src/clients/createClient.test.ts +++ b/packages/core/src/clients/createClient.test.ts @@ -1,61 +1,61 @@ -import { createClient, type Client } from "./createClient.js"; -import { viemConnector } from "./ethereum/viemConnector.js"; +import { type Client, createClient } from './createClient.js' +import { viemConnector } from './ethereum/viemConnector.js' -describe("createClient", () => { - const ethereum = viemConnector(); +describe('createClient', () => { + const ethereum = viemConnector() const config = { ethereum, - }; + } - let client: Client; + let client: Client beforeEach(() => { - client = createClient(config); - }); + client = createClient(config) + }) - test("adds defaults to config", () => { + test('adds defaults to config', () => { expect(client.config).toEqual({ - relay: "https://relay.farcaster.xyz", - version: "v1", - }); - }); + relay: 'https://relay.farcaster.xyz', + version: 'v1', + }) + }) - test("overrides version", () => { + test('overrides version', () => { client = createClient({ ...config, - version: "v2", - }); + version: 'v2', + }) expect(client.config).toEqual({ - relay: "https://relay.farcaster.xyz", - version: "v2", - }); - }); + relay: 'https://relay.farcaster.xyz', + version: 'v2', + }) + }) - test("overrides relay", () => { + test('overrides relay', () => { client = createClient({ ...config, - relay: "https://custom-server.example.com", - }); + relay: 'https://custom-server.example.com', + }) expect(client.config).toEqual({ - relay: "https://custom-server.example.com", - version: "v1", - }); - }); + relay: 'https://custom-server.example.com', + version: 'v1', + }) + }) - test("includes no actions", () => { + test('includes no actions', () => { client = createClient({ ...config, - version: "v2", - }); + version: 'v2', + }) expect(client).toEqual({ config: { - relay: "https://relay.farcaster.xyz", - version: "v2", + relay: 'https://relay.farcaster.xyz', + version: 'v2', }, ethereum, - }); - }); -}); + }) + }) +}) diff --git a/packages/core/src/clients/createClient.ts b/packages/core/src/clients/createClient.ts index 132d4db..5157eb8 100644 --- a/packages/core/src/clients/createClient.ts +++ b/packages/core/src/clients/createClient.ts @@ -1,29 +1,32 @@ -import { type EthereumConnector } from "./ethereum/connector.js"; +import type { EthereumConnector } from './ethereum/connector.js' export interface CreateClientParameters { - relay?: string; - version?: string; - ethereum: EthereumConnector; + relay?: string + version?: string + ethereum: EthereumConnector } export interface ClientConfig { - relay: string; - version?: string; + relay: string + version?: string } export interface Client { - config: ClientConfig; - ethereum: EthereumConnector; + config: ClientConfig + ethereum: EthereumConnector } const configDefaults = { - relay: "https://relay.farcaster.xyz", - version: "v1", -}; + relay: 'https://relay.farcaster.xyz', + version: 'v1', +} -export const createClient = ({ ethereum, ...config }: CreateClientParameters) => { +export const createClient = ({ + ethereum, + ...config +}: CreateClientParameters) => { return { config: { ...configDefaults, ...config }, ethereum, - }; -}; + } +} diff --git a/packages/core/src/clients/createWalletClient.test.ts b/packages/core/src/clients/createWalletClient.test.ts index 2d186b5..6b5a830 100644 --- a/packages/core/src/clients/createWalletClient.test.ts +++ b/packages/core/src/clients/createWalletClient.test.ts @@ -1,37 +1,37 @@ -import { createWalletClient, type WalletClient } from "./createWalletClient.js"; -import { viemConnector } from "./ethereum/viemConnector.js"; +import { type WalletClient, createWalletClient } from './createWalletClient.js' +import { viemConnector } from './ethereum/viemConnector.js' -describe("createWalletClient", () => { +describe('createWalletClient', () => { const config = { ethereum: viemConnector(), - }; + } - let walletClient: WalletClient; + let walletClient: WalletClient beforeEach(() => { - walletClient = createWalletClient(config); - }); + walletClient = createWalletClient(config) + }) - test("adds version to config", () => { + test('adds version to config', () => { expect(walletClient.config).toEqual({ - relay: "https://relay.farcaster.xyz", - version: "v1", - }); - }); + relay: 'https://relay.farcaster.xyz', + version: 'v1', + }) + }) - test("overrides version", () => { + test('overrides version', () => { walletClient = createWalletClient({ ...config, - version: "v2", - }); + version: 'v2', + }) expect(walletClient.config).toEqual({ - relay: "https://relay.farcaster.xyz", - version: "v2", - }); - }); + relay: 'https://relay.farcaster.xyz', + version: 'v2', + }) + }) - test("includes app actions", () => { - expect(walletClient.authenticate).toBeDefined(); - }); -}); + test('includes app actions', () => { + expect(walletClient.authenticate).toBeDefined() + }) +}) diff --git a/packages/core/src/clients/createWalletClient.ts b/packages/core/src/clients/createWalletClient.ts index 2c66460..670ee1f 100644 --- a/packages/core/src/clients/createWalletClient.ts +++ b/packages/core/src/clients/createWalletClient.ts @@ -1,18 +1,26 @@ import { - authenticate, type AuthenticateParameters, type AuthenticateReturnType, -} from "../actions/auth/authenticate.js"; -import { type Client, type CreateClientParameters, createClient } from "./createClient.js"; + authenticate, +} from '../actions/auth/authenticate.js' +import { + type Client, + type CreateClientParameters, + createClient, +} from './createClient.js' export interface WalletClient extends Client { - authenticate: (args: AuthenticateParameters) => Promise; + authenticate: ( + args: AuthenticateParameters, + ) => Promise } -export const createWalletClient = (config: CreateClientParameters): WalletClient => { - const client = createClient(config); +export const createWalletClient = ( + config: CreateClientParameters, +): WalletClient => { + const client = createClient(config) return { ...client, authenticate: (args: AuthenticateParameters) => authenticate(client, args), - }; -}; + } +} diff --git a/packages/core/src/clients/ethereum/connector.ts b/packages/core/src/clients/ethereum/connector.ts index 2b0da79..0c45f68 100644 --- a/packages/core/src/clients/ethereum/connector.ts +++ b/packages/core/src/clients/ethereum/connector.ts @@ -1,5 +1,5 @@ -import { type Hex } from "viem"; +import type { Hex } from 'viem' export interface EthereumConnector { - getFid: (custody: Hex) => Promise; + getFid: (custody: Hex) => Promise } diff --git a/packages/core/src/clients/ethereum/index.ts b/packages/core/src/clients/ethereum/index.ts index fbe1e47..e12c095 100644 --- a/packages/core/src/clients/ethereum/index.ts +++ b/packages/core/src/clients/ethereum/index.ts @@ -1 +1 @@ -export * from "./viemConnector.js"; +export * from './viemConnector.js' diff --git a/packages/core/src/clients/ethereum/viemConnector.ts b/packages/core/src/clients/ethereum/viemConnector.ts index dededc7..a220f2f 100644 --- a/packages/core/src/clients/ethereum/viemConnector.ts +++ b/packages/core/src/clients/ethereum/viemConnector.ts @@ -1,28 +1,31 @@ -import { type Hex, createPublicClient, http } from "viem"; -import { optimism } from "viem/chains"; -import { ID_REGISTRY_ADDRESS, idRegistryABI } from "../../contracts/idRegistry.js"; -import { type EthereumConnector } from "./connector.js"; +import { http, type Hex, createPublicClient } from 'viem' +import { optimism } from 'viem/chains' +import { + ID_REGISTRY_ADDRESS, + idRegistryABI, +} from '../../contracts/idRegistry.js' +import type { EthereumConnector } from './connector.js' interface ViemConfigArgs { - rpcUrl?: string; + rpcUrl?: string } export const viemConnector = (args?: ViemConfigArgs): EthereumConnector => { const publicClient = createPublicClient({ chain: optimism, transport: http(args?.rpcUrl), - }); + }) const getFid = async (custody: Hex): Promise => { return publicClient.readContract({ address: ID_REGISTRY_ADDRESS, abi: idRegistryABI, - functionName: "idOf", + functionName: 'idOf', args: [custody], - }); - }; + }) + } return { getFid, - }; -}; + } +} diff --git a/packages/core/src/clients/index.ts b/packages/core/src/clients/index.ts index 2aeea97..a803b24 100644 --- a/packages/core/src/clients/index.ts +++ b/packages/core/src/clients/index.ts @@ -1,5 +1,5 @@ -export * from "./createAppClient.js"; -export * from "./createWalletClient.js"; -export * from "./createClient.js"; -export * from "./ethereum/index.js"; -export * from "./transports/index.js"; +export * from './createAppClient.js' +export * from './createWalletClient.js' +export * from './createClient.js' +export * from './ethereum/index.js' +export * from './transports/index.js' diff --git a/packages/core/src/clients/transports/http.test.ts b/packages/core/src/clients/transports/http.test.ts index 12f95af..681ca22 100644 --- a/packages/core/src/clients/transports/http.test.ts +++ b/packages/core/src/clients/transports/http.test.ts @@ -1,151 +1,156 @@ -import { createClient } from "../createClient.js"; -import { viemConnector } from "../ethereum/viemConnector.js"; -import { get, poll, post } from "./http.js"; -import { jest } from "@jest/globals"; +import { jest } from '@jest/globals' +import { createClient } from '../createClient.js' +import { viemConnector } from '../ethereum/viemConnector.js' +import { get, poll, post } from './http.js' -describe("http", () => { +describe('http', () => { const config = { - relay: "https://relay.farcaster.xyz", + relay: 'https://relay.farcaster.xyz', ethereum: viemConnector(), - }; + } - const client = createClient(config); + const client = createClient(config) - const bodyData = { data: "response stub" }; - let httpResponse: Response; + const bodyData = { data: 'response stub' } + let httpResponse: Response beforeEach(() => { - httpResponse = new Response(JSON.stringify(bodyData)); - }); + httpResponse = new Response(JSON.stringify(bodyData)) + }) afterEach(() => { - jest.restoreAllMocks(); - }); + jest.restoreAllMocks() + }) - describe("get", () => { - test("returns parsed body data", async () => { - jest.spyOn(global, "fetch").mockResolvedValue(httpResponse); + describe('get', () => { + test('returns parsed body data', async () => { + jest.spyOn(global, 'fetch').mockResolvedValue(httpResponse) - const data = await get(client, "path"); + const data = await get(client, 'path') - expect(data).toEqual(bodyData); - }); + expect(data).toEqual(bodyData) + }) - test("constructs fetch request", async () => { - const spy = jest.spyOn(global, "fetch").mockResolvedValue(httpResponse); + test('constructs fetch request', async () => { + const spy = jest.spyOn(global, 'fetch').mockResolvedValue(httpResponse) - await get(client, "path"); + await get(client, 'path') - expect(spy).toHaveBeenCalledTimes(1); - expect(spy).toHaveBeenCalledWith("https://relay.farcaster.xyz/v1/path", { - headers: { "Content-Type": "application/json" }, - }); - }); + expect(spy).toHaveBeenCalledTimes(1) + expect(spy).toHaveBeenCalledWith('https://relay.farcaster.xyz/v1/path', { + headers: { 'Content-Type': 'application/json' }, + }) + }) - test("adds optional params", async () => { - const spy = jest.spyOn(global, "fetch").mockResolvedValue(httpResponse); + test('adds optional params', async () => { + const spy = jest.spyOn(global, 'fetch').mockResolvedValue(httpResponse) - await get(client, "path", { - authToken: "some-auth-token", - headers: { "X-Some-Header": "some-header-value" }, - }); + await get(client, 'path', { + authToken: 'some-auth-token', + headers: { 'X-Some-Header': 'some-header-value' }, + }) - expect(spy).toHaveBeenCalledTimes(1); - expect(spy).toHaveBeenCalledWith("https://relay.farcaster.xyz/v1/path", { + expect(spy).toHaveBeenCalledTimes(1) + expect(spy).toHaveBeenCalledWith('https://relay.farcaster.xyz/v1/path', { headers: { - "Content-Type": "application/json", - Authorization: "Bearer some-auth-token", - "X-Some-Header": "some-header-value", + 'Content-Type': 'application/json', + Authorization: 'Bearer some-auth-token', + 'X-Some-Header': 'some-header-value', }, - }); - }); - }); + }) + }) + }) - describe("post", () => { - test("returns fetch response", async () => { - jest.spyOn(global, "fetch").mockResolvedValue(httpResponse); + describe('post', () => { + test('returns fetch response', async () => { + jest.spyOn(global, 'fetch').mockResolvedValue(httpResponse) - const requestData = { data: "request stub" }; - const data = await post(client, "path", requestData); + const requestData = { data: 'request stub' } + const data = await post(client, 'path', requestData) - expect(data).toEqual(bodyData); - }); + expect(data).toEqual(bodyData) + }) - test("returns parsed body data", async () => { - jest.spyOn(global, "fetch").mockResolvedValue(httpResponse); + test('returns parsed body data', async () => { + jest.spyOn(global, 'fetch').mockResolvedValue(httpResponse) - const requestData = { data: "request stub" }; - const data = await post(client, "path", requestData); + const requestData = { data: 'request stub' } + const data = await post(client, 'path', requestData) - expect(data).toEqual(bodyData); - }); + expect(data).toEqual(bodyData) + }) - test("constructs fetch request", async () => { - const spy = jest.spyOn(global, "fetch").mockResolvedValue(httpResponse); + test('constructs fetch request', async () => { + const spy = jest.spyOn(global, 'fetch').mockResolvedValue(httpResponse) - const requestData = { data: "request stub" }; - await post(client, "path", requestData, { - authToken: "some-auth-token", - headers: { "X-Some-Header": "some-header-value" }, - }); + const requestData = { data: 'request stub' } + await post(client, 'path', requestData, { + authToken: 'some-auth-token', + headers: { 'X-Some-Header': 'some-header-value' }, + }) - expect(spy).toHaveBeenCalledTimes(1); - expect(spy).toHaveBeenCalledWith("https://relay.farcaster.xyz/v1/path", { - method: "POST", + expect(spy).toHaveBeenCalledTimes(1) + expect(spy).toHaveBeenCalledWith('https://relay.farcaster.xyz/v1/path', { + method: 'POST', body: JSON.stringify(requestData), headers: { - "Content-Type": "application/json", - Authorization: "Bearer some-auth-token", - "X-Some-Header": "some-header-value", + 'Content-Type': 'application/json', + Authorization: 'Bearer some-auth-token', + 'X-Some-Header': 'some-header-value', }, - }); - }); - }); - - describe("poll", () => { - test("polls for success response", async () => { - let i = 0; - const spy = jest.spyOn(global, "fetch").mockImplementation(async () => { + }) + }) + }) + + describe('poll', () => { + test('polls for success response', async () => { + let i = 0 + const spy = jest.spyOn(global, 'fetch').mockImplementation(async () => { if (i === 2) - return new Response(JSON.stringify({ state: "completed" }), { + return new Response(JSON.stringify({ state: 'completed' }), { status: 200, - }); - i++; - return new Response(JSON.stringify({ state: "pending" }), { + }) + i++ + return new Response(JSON.stringify({ state: 'pending' }), { status: 202, - }); - }); + }) + }) // .mockResolvedValueOnce(accepted1) // .mockResolvedValueOnce(accepted2) // .mockResolvedValueOnce(ok); let data: { state: string } = { - state: "error", - }; - for await (const generatorResponse of poll(client, "path", { interval: 100 })) { - data = generatorResponse; - if (generatorResponse.state === "completed") break; + state: 'error', + } + for await (const generatorResponse of poll(client, 'path', { + interval: 100, + })) { + data = generatorResponse + if (generatorResponse.state === 'completed') break } - expect(spy).toHaveBeenCalledTimes(3); - expect(data).toEqual({ state: "completed" }); - }); + expect(spy).toHaveBeenCalledTimes(3) + expect(data).toEqual({ state: 'completed' }) + }) - test("times out", async () => { - const accepted = new Response(JSON.stringify({ state: "pending" }), { + test('times out', async () => { + const accepted = new Response(JSON.stringify({ state: 'pending' }), { status: 202, - }); + }) - jest.spyOn(global, "fetch").mockResolvedValue(accepted); + jest.spyOn(global, 'fetch').mockResolvedValue(accepted) - let i = 0; + let i = 0 try { - for await (const _generatorResponse of poll(client, "path", { timeout: 1, interval: 1 })) { - if (i++ === 1) expect(true).toBe(false); + for await (const _generatorResponse of poll(client, 'path', { + timeout: 1, + interval: 1, + })) { + if (i++ === 1) expect(true).toBe(false) } } catch (e: unknown) { - expect((e as Error).message).toBe("Polling timed out after 1ms"); + expect((e as Error).message).toBe('Polling timed out after 1ms') } - }); - }); -}); + }) + }) +}) diff --git a/packages/core/src/clients/transports/http.ts b/packages/core/src/clients/transports/http.ts index 8ccd613..1006295 100644 --- a/packages/core/src/clients/transports/http.ts +++ b/packages/core/src/clients/transports/http.ts @@ -1,28 +1,28 @@ -import { type Client } from "../createClient.js"; -import { AuthClientError } from "../../errors.js"; +import { AuthClientError } from '../../errors.js' +import type { Client } from '../createClient.js' export interface HttpOpts { - channelToken?: string; - headers?: Record; + channelToken?: string + headers?: Record } export interface PollOpts { - interval?: number; - timeout?: number; - conditionToReturn?: (data: BodyType) => boolean; + interval?: number + timeout?: number + conditionToReturn?: (data: BodyType) => boolean } export interface HttpResponse { - response: Response; - data: ResponseDataType; + response: Response + data: ResponseDataType } -export type AsyncHttpResponse = Promise>; +export type AsyncHttpResponse = Promise> const defaultPollOpts = { interval: 1000, timeout: 10000, -}; +} export const get = async ( client: Client, @@ -32,17 +32,17 @@ export const get = async ( try { const response = await fetch(getURI(client, path), { headers: getHeaders(opts), - }); + }) if (!response.ok) { - throw new AuthClientError("unknown", new Error(response.statusText)); + throw new AuthClientError('unknown', new Error(response.statusText)) } - return await response.json(); + return await response.json() } catch (error) { - throw new AuthClientError("unknown", error as Error); + throw new AuthClientError('unknown', error as Error) } -}; +} export const post = async ( client: Client, @@ -52,20 +52,20 @@ export const post = async ( ): Promise => { try { const response = await fetch(getURI(client, path), { - method: "POST", + method: 'POST', body: JSON.stringify(parameters), headers: getHeaders(opts), - }); + }) if (!response.ok) { - throw new AuthClientError("unknown", new Error(response.statusText)); + throw new AuthClientError('unknown', new Error(response.statusText)) } - return await response.json(); + return await response.json() } catch (error) { - throw new AuthClientError("unknown", error as Error); + throw new AuthClientError('unknown', error as Error) } -}; +} export async function* poll( client: Client, @@ -76,29 +76,32 @@ export async function* poll( const { timeout, interval } = { ...defaultPollOpts, ...pollOpts, - }; + } - const deadline = Date.now() + timeout; + const deadline = Date.now() + timeout while (Date.now() < deadline) { - const data = await get(client, path, opts); - if (pollOpts?.conditionToReturn?.(data)) return data; - yield data; - await new Promise((resolve) => setTimeout(resolve, interval)); + const data = await get(client, path, opts) + if (pollOpts?.conditionToReturn?.(data)) return data + yield data + await new Promise((resolve) => setTimeout(resolve, interval)) } - throw new AuthClientError("unavailable", `Polling timed out after ${timeout}ms`); + throw new AuthClientError( + 'unavailable', + `Polling timed out after ${timeout}ms`, + ) } const getURI = (client: Client, path: string) => { - return `${client.config.relay}/${client.config.version}/${path}`; -}; + return `${client.config.relay}/${client.config.version}/${path}` +} const getHeaders = (opts?: HttpOpts) => { const headers = { ...opts?.headers, - }; + } if (opts?.channelToken) { - headers["Authorization"] = `Bearer ${opts.channelToken}`; + headers.Authorization = `Bearer ${opts.channelToken}` } - return { ...headers, "Content-Type": "application/json" }; -}; + return { ...headers, 'Content-Type': 'application/json' } +} diff --git a/packages/core/src/clients/transports/index.ts b/packages/core/src/clients/transports/index.ts index 080c36c..d2790f1 100644 --- a/packages/core/src/clients/transports/index.ts +++ b/packages/core/src/clients/transports/index.ts @@ -1 +1 @@ -export * from "./http.js"; +export * from './http.js' diff --git a/packages/core/src/contracts/idRegistry.ts b/packages/core/src/contracts/idRegistry.ts index 87e48df..2db8d4d 100644 --- a/packages/core/src/contracts/idRegistry.ts +++ b/packages/core/src/contracts/idRegistry.ts @@ -2,1255 +2,1256 @@ export const idRegistryABI = [ { inputs: [ { - internalType: "address", - name: "_migrator", - type: "address", + internalType: 'address', + name: '_migrator', + type: 'address', }, { - internalType: "address", - name: "_initialOwner", - type: "address", + internalType: 'address', + name: '_initialOwner', + type: 'address', }, ], - stateMutability: "nonpayable", - type: "constructor", + stateMutability: 'nonpayable', + type: 'constructor', }, { inputs: [], - name: "AlreadyMigrated", - type: "error", + name: 'AlreadyMigrated', + type: 'error', }, { inputs: [], - name: "GatewayFrozen", - type: "error", + name: 'GatewayFrozen', + type: 'error', }, { inputs: [], - name: "HasId", - type: "error", + name: 'HasId', + type: 'error', }, { inputs: [], - name: "HasNoId", - type: "error", + name: 'HasNoId', + type: 'error', }, { inputs: [ { - internalType: "address", - name: "account", - type: "address", + internalType: 'address', + name: 'account', + type: 'address', }, { - internalType: "uint256", - name: "currentNonce", - type: "uint256", + internalType: 'uint256', + name: 'currentNonce', + type: 'uint256', }, ], - name: "InvalidAccountNonce", - type: "error", + name: 'InvalidAccountNonce', + type: 'error', }, { inputs: [], - name: "InvalidShortString", - type: "error", + name: 'InvalidShortString', + type: 'error', }, { inputs: [], - name: "InvalidSignature", - type: "error", + name: 'InvalidSignature', + type: 'error', }, { inputs: [], - name: "OnlyGuardian", - type: "error", + name: 'OnlyGuardian', + type: 'error', }, { inputs: [], - name: "OnlyMigrator", - type: "error", + name: 'OnlyMigrator', + type: 'error', }, { inputs: [], - name: "PermissionRevoked", - type: "error", + name: 'PermissionRevoked', + type: 'error', }, { inputs: [], - name: "SignatureExpired", - type: "error", + name: 'SignatureExpired', + type: 'error', }, { inputs: [ { - internalType: "string", - name: "str", - type: "string", + internalType: 'string', + name: 'str', + type: 'string', }, ], - name: "StringTooLong", - type: "error", + name: 'StringTooLong', + type: 'error', }, { inputs: [], - name: "Unauthorized", - type: "error", + name: 'Unauthorized', + type: 'error', }, { anonymous: false, inputs: [ { indexed: true, - internalType: "address", - name: "guardian", - type: "address", + internalType: 'address', + name: 'guardian', + type: 'address', }, ], - name: "Add", - type: "event", + name: 'Add', + type: 'event', }, { anonymous: false, inputs: [ { indexed: true, - internalType: "uint256", - name: "fid", - type: "uint256", + internalType: 'uint256', + name: 'fid', + type: 'uint256', }, ], - name: "AdminReset", - type: "event", + name: 'AdminReset', + type: 'event', }, { anonymous: false, inputs: [ { indexed: true, - internalType: "uint256", - name: "id", - type: "uint256", + internalType: 'uint256', + name: 'id', + type: 'uint256', }, { indexed: true, - internalType: "address", - name: "recovery", - type: "address", + internalType: 'address', + name: 'recovery', + type: 'address', }, ], - name: "ChangeRecoveryAddress", - type: "event", + name: 'ChangeRecoveryAddress', + type: 'event', }, { anonymous: false, inputs: [], - name: "EIP712DomainChanged", - type: "event", + name: 'EIP712DomainChanged', + type: 'event', }, { anonymous: false, inputs: [ { indexed: false, - internalType: "address", - name: "idGateway", - type: "address", + internalType: 'address', + name: 'idGateway', + type: 'address', }, ], - name: "FreezeIdGateway", - type: "event", + name: 'FreezeIdGateway', + type: 'event', }, { anonymous: false, inputs: [ { indexed: true, - internalType: "uint256", - name: "migratedAt", - type: "uint256", + internalType: 'uint256', + name: 'migratedAt', + type: 'uint256', }, ], - name: "Migrated", - type: "event", + name: 'Migrated', + type: 'event', }, { anonymous: false, inputs: [ { indexed: true, - internalType: "address", - name: "previousOwner", - type: "address", + internalType: 'address', + name: 'previousOwner', + type: 'address', }, { indexed: true, - internalType: "address", - name: "newOwner", - type: "address", + internalType: 'address', + name: 'newOwner', + type: 'address', }, ], - name: "OwnershipTransferStarted", - type: "event", + name: 'OwnershipTransferStarted', + type: 'event', }, { anonymous: false, inputs: [ { indexed: true, - internalType: "address", - name: "previousOwner", - type: "address", + internalType: 'address', + name: 'previousOwner', + type: 'address', }, { indexed: true, - internalType: "address", - name: "newOwner", - type: "address", + internalType: 'address', + name: 'newOwner', + type: 'address', }, ], - name: "OwnershipTransferred", - type: "event", + name: 'OwnershipTransferred', + type: 'event', }, { anonymous: false, inputs: [ { indexed: false, - internalType: "address", - name: "account", - type: "address", + internalType: 'address', + name: 'account', + type: 'address', }, ], - name: "Paused", - type: "event", + name: 'Paused', + type: 'event', }, { anonymous: false, inputs: [ { indexed: true, - internalType: "address", - name: "from", - type: "address", + internalType: 'address', + name: 'from', + type: 'address', }, { indexed: true, - internalType: "address", - name: "to", - type: "address", + internalType: 'address', + name: 'to', + type: 'address', }, { indexed: true, - internalType: "uint256", - name: "id", - type: "uint256", + internalType: 'uint256', + name: 'id', + type: 'uint256', }, ], - name: "Recover", - type: "event", + name: 'Recover', + type: 'event', }, { anonymous: false, inputs: [ { indexed: true, - internalType: "address", - name: "to", - type: "address", + internalType: 'address', + name: 'to', + type: 'address', }, { indexed: true, - internalType: "uint256", - name: "id", - type: "uint256", + internalType: 'uint256', + name: 'id', + type: 'uint256', }, { indexed: false, - internalType: "address", - name: "recovery", - type: "address", + internalType: 'address', + name: 'recovery', + type: 'address', }, ], - name: "Register", - type: "event", + name: 'Register', + type: 'event', }, { anonymous: false, inputs: [ { indexed: true, - internalType: "address", - name: "guardian", - type: "address", + internalType: 'address', + name: 'guardian', + type: 'address', }, ], - name: "Remove", - type: "event", + name: 'Remove', + type: 'event', }, { anonymous: false, inputs: [ { indexed: false, - internalType: "uint256", - name: "oldCounter", - type: "uint256", + internalType: 'uint256', + name: 'oldCounter', + type: 'uint256', }, { indexed: false, - internalType: "uint256", - name: "newCounter", - type: "uint256", + internalType: 'uint256', + name: 'newCounter', + type: 'uint256', }, ], - name: "SetIdCounter", - type: "event", + name: 'SetIdCounter', + type: 'event', }, { anonymous: false, inputs: [ { indexed: false, - internalType: "address", - name: "oldIdGateway", - type: "address", + internalType: 'address', + name: 'oldIdGateway', + type: 'address', }, { indexed: false, - internalType: "address", - name: "newIdGateway", - type: "address", + internalType: 'address', + name: 'newIdGateway', + type: 'address', }, ], - name: "SetIdGateway", - type: "event", + name: 'SetIdGateway', + type: 'event', }, { anonymous: false, inputs: [ { indexed: false, - internalType: "address", - name: "oldMigrator", - type: "address", + internalType: 'address', + name: 'oldMigrator', + type: 'address', }, { indexed: false, - internalType: "address", - name: "newMigrator", - type: "address", + internalType: 'address', + name: 'newMigrator', + type: 'address', }, ], - name: "SetMigrator", - type: "event", + name: 'SetMigrator', + type: 'event', }, { anonymous: false, inputs: [ { indexed: true, - internalType: "address", - name: "from", - type: "address", + internalType: 'address', + name: 'from', + type: 'address', }, { indexed: true, - internalType: "address", - name: "to", - type: "address", + internalType: 'address', + name: 'to', + type: 'address', }, { indexed: true, - internalType: "uint256", - name: "id", - type: "uint256", + internalType: 'uint256', + name: 'id', + type: 'uint256', }, ], - name: "Transfer", - type: "event", + name: 'Transfer', + type: 'event', }, { anonymous: false, inputs: [ { indexed: false, - internalType: "address", - name: "account", - type: "address", + internalType: 'address', + name: 'account', + type: 'address', }, ], - name: "Unpaused", - type: "event", + name: 'Unpaused', + type: 'event', }, { inputs: [], - name: "CHANGE_RECOVERY_ADDRESS_TYPEHASH", + name: 'CHANGE_RECOVERY_ADDRESS_TYPEHASH', outputs: [ { - internalType: "bytes32", - name: "", - type: "bytes32", + internalType: 'bytes32', + name: '', + type: 'bytes32', }, ], - stateMutability: "view", - type: "function", + stateMutability: 'view', + type: 'function', }, { inputs: [], - name: "TRANSFER_AND_CHANGE_RECOVERY_TYPEHASH", + name: 'TRANSFER_AND_CHANGE_RECOVERY_TYPEHASH', outputs: [ { - internalType: "bytes32", - name: "", - type: "bytes32", + internalType: 'bytes32', + name: '', + type: 'bytes32', }, ], - stateMutability: "view", - type: "function", + stateMutability: 'view', + type: 'function', }, { inputs: [], - name: "TRANSFER_TYPEHASH", + name: 'TRANSFER_TYPEHASH', outputs: [ { - internalType: "bytes32", - name: "", - type: "bytes32", + internalType: 'bytes32', + name: '', + type: 'bytes32', }, ], - stateMutability: "view", - type: "function", + stateMutability: 'view', + type: 'function', }, { inputs: [], - name: "VERSION", + name: 'VERSION', outputs: [ { - internalType: "string", - name: "", - type: "string", + internalType: 'string', + name: '', + type: 'string', }, ], - stateMutability: "view", - type: "function", + stateMutability: 'view', + type: 'function', }, { inputs: [], - name: "acceptOwnership", + name: 'acceptOwnership', outputs: [], - stateMutability: "nonpayable", - type: "function", + stateMutability: 'nonpayable', + type: 'function', }, { inputs: [ { - internalType: "address", - name: "guardian", - type: "address", + internalType: 'address', + name: 'guardian', + type: 'address', }, ], - name: "addGuardian", + name: 'addGuardian', outputs: [], - stateMutability: "nonpayable", - type: "function", + stateMutability: 'nonpayable', + type: 'function', }, { inputs: [ { components: [ { - internalType: "uint24", - name: "fid", - type: "uint24", + internalType: 'uint24', + name: 'fid', + type: 'uint24', }, { - internalType: "address", - name: "custody", - type: "address", + internalType: 'address', + name: 'custody', + type: 'address', }, { - internalType: "address", - name: "recovery", - type: "address", + internalType: 'address', + name: 'recovery', + type: 'address', }, ], - internalType: "struct IIdRegistry.BulkRegisterData[]", - name: "ids", - type: "tuple[]", + internalType: 'struct IIdRegistry.BulkRegisterData[]', + name: 'ids', + type: 'tuple[]', }, ], - name: "bulkRegisterIds", + name: 'bulkRegisterIds', outputs: [], - stateMutability: "nonpayable", - type: "function", + stateMutability: 'nonpayable', + type: 'function', }, { inputs: [ { components: [ { - internalType: "uint24", - name: "fid", - type: "uint24", + internalType: 'uint24', + name: 'fid', + type: 'uint24', }, { - internalType: "address", - name: "custody", - type: "address", + internalType: 'address', + name: 'custody', + type: 'address', }, ], - internalType: "struct IIdRegistry.BulkRegisterDefaultRecoveryData[]", - name: "ids", - type: "tuple[]", + internalType: 'struct IIdRegistry.BulkRegisterDefaultRecoveryData[]', + name: 'ids', + type: 'tuple[]', }, { - internalType: "address", - name: "recovery", - type: "address", + internalType: 'address', + name: 'recovery', + type: 'address', }, ], - name: "bulkRegisterIdsWithDefaultRecovery", + name: 'bulkRegisterIdsWithDefaultRecovery', outputs: [], - stateMutability: "nonpayable", - type: "function", + stateMutability: 'nonpayable', + type: 'function', }, { inputs: [ { - internalType: "uint24[]", - name: "ids", - type: "uint24[]", + internalType: 'uint24[]', + name: 'ids', + type: 'uint24[]', }, ], - name: "bulkResetIds", + name: 'bulkResetIds', outputs: [], - stateMutability: "nonpayable", - type: "function", + stateMutability: 'nonpayable', + type: 'function', }, { inputs: [ { - internalType: "address", - name: "recovery", - type: "address", + internalType: 'address', + name: 'recovery', + type: 'address', }, ], - name: "changeRecoveryAddress", + name: 'changeRecoveryAddress', outputs: [], - stateMutability: "nonpayable", - type: "function", + stateMutability: 'nonpayable', + type: 'function', }, { inputs: [ { - internalType: "address", - name: "owner", - type: "address", + internalType: 'address', + name: 'owner', + type: 'address', }, { - internalType: "address", - name: "recovery", - type: "address", + internalType: 'address', + name: 'recovery', + type: 'address', }, { - internalType: "uint256", - name: "deadline", - type: "uint256", + internalType: 'uint256', + name: 'deadline', + type: 'uint256', }, { - internalType: "bytes", - name: "sig", - type: "bytes", + internalType: 'bytes', + name: 'sig', + type: 'bytes', }, ], - name: "changeRecoveryAddressFor", + name: 'changeRecoveryAddressFor', outputs: [], - stateMutability: "nonpayable", - type: "function", + stateMutability: 'nonpayable', + type: 'function', }, { inputs: [ { - internalType: "uint256", - name: "fid", - type: "uint256", + internalType: 'uint256', + name: 'fid', + type: 'uint256', }, ], - name: "custodyOf", + name: 'custodyOf', outputs: [ { - internalType: "address", - name: "custody", - type: "address", + internalType: 'address', + name: 'custody', + type: 'address', }, ], - stateMutability: "view", - type: "function", + stateMutability: 'view', + type: 'function', }, { inputs: [], - name: "domainSeparatorV4", + name: 'domainSeparatorV4', outputs: [ { - internalType: "bytes32", - name: "", - type: "bytes32", + internalType: 'bytes32', + name: '', + type: 'bytes32', }, ], - stateMutability: "view", - type: "function", + stateMutability: 'view', + type: 'function', }, { inputs: [], - name: "eip712Domain", + name: 'eip712Domain', outputs: [ { - internalType: "bytes1", - name: "fields", - type: "bytes1", + internalType: 'bytes1', + name: 'fields', + type: 'bytes1', }, { - internalType: "string", - name: "name", - type: "string", + internalType: 'string', + name: 'name', + type: 'string', }, { - internalType: "string", - name: "version", - type: "string", + internalType: 'string', + name: 'version', + type: 'string', }, { - internalType: "uint256", - name: "chainId", - type: "uint256", + internalType: 'uint256', + name: 'chainId', + type: 'uint256', }, { - internalType: "address", - name: "verifyingContract", - type: "address", + internalType: 'address', + name: 'verifyingContract', + type: 'address', }, { - internalType: "bytes32", - name: "salt", - type: "bytes32", + internalType: 'bytes32', + name: 'salt', + type: 'bytes32', }, { - internalType: "uint256[]", - name: "extensions", - type: "uint256[]", + internalType: 'uint256[]', + name: 'extensions', + type: 'uint256[]', }, ], - stateMutability: "view", - type: "function", + stateMutability: 'view', + type: 'function', }, { inputs: [], - name: "freezeIdGateway", + name: 'freezeIdGateway', outputs: [], - stateMutability: "nonpayable", - type: "function", + stateMutability: 'nonpayable', + type: 'function', }, { inputs: [], - name: "gatewayFrozen", + name: 'gatewayFrozen', outputs: [ { - internalType: "bool", - name: "", - type: "bool", + internalType: 'bool', + name: '', + type: 'bool', }, ], - stateMutability: "view", - type: "function", + stateMutability: 'view', + type: 'function', }, { inputs: [], - name: "gracePeriod", + name: 'gracePeriod', outputs: [ { - internalType: "uint24", - name: "", - type: "uint24", + internalType: 'uint24', + name: '', + type: 'uint24', }, ], - stateMutability: "view", - type: "function", + stateMutability: 'view', + type: 'function', }, { inputs: [ { - internalType: "address", - name: "guardian", - type: "address", + internalType: 'address', + name: 'guardian', + type: 'address', }, ], - name: "guardians", + name: 'guardians', outputs: [ { - internalType: "bool", - name: "isGuardian", - type: "bool", + internalType: 'bool', + name: 'isGuardian', + type: 'bool', }, ], - stateMutability: "view", - type: "function", + stateMutability: 'view', + type: 'function', }, { inputs: [ { - internalType: "bytes32", - name: "structHash", - type: "bytes32", + internalType: 'bytes32', + name: 'structHash', + type: 'bytes32', }, ], - name: "hashTypedDataV4", + name: 'hashTypedDataV4', outputs: [ { - internalType: "bytes32", - name: "", - type: "bytes32", + internalType: 'bytes32', + name: '', + type: 'bytes32', }, ], - stateMutability: "view", - type: "function", + stateMutability: 'view', + type: 'function', }, { inputs: [], - name: "idCounter", + name: 'idCounter', outputs: [ { - internalType: "uint256", - name: "", - type: "uint256", + internalType: 'uint256', + name: '', + type: 'uint256', }, ], - stateMutability: "view", - type: "function", + stateMutability: 'view', + type: 'function', }, { inputs: [], - name: "idGateway", + name: 'idGateway', outputs: [ { - internalType: "address", - name: "", - type: "address", + internalType: 'address', + name: '', + type: 'address', }, ], - stateMutability: "view", - type: "function", + stateMutability: 'view', + type: 'function', }, { inputs: [ { - internalType: "address", - name: "owner", - type: "address", + internalType: 'address', + name: 'owner', + type: 'address', }, ], - name: "idOf", + name: 'idOf', outputs: [ { - internalType: "uint256", - name: "fid", - type: "uint256", + internalType: 'uint256', + name: 'fid', + type: 'uint256', }, ], - stateMutability: "view", - type: "function", + stateMutability: 'view', + type: 'function', }, { inputs: [], - name: "isMigrated", + name: 'isMigrated', outputs: [ { - internalType: "bool", - name: "", - type: "bool", + internalType: 'bool', + name: '', + type: 'bool', }, ], - stateMutability: "view", - type: "function", + stateMutability: 'view', + type: 'function', }, { inputs: [], - name: "migrate", + name: 'migrate', outputs: [], - stateMutability: "nonpayable", - type: "function", + stateMutability: 'nonpayable', + type: 'function', }, { inputs: [], - name: "migratedAt", + name: 'migratedAt', outputs: [ { - internalType: "uint40", - name: "", - type: "uint40", + internalType: 'uint40', + name: '', + type: 'uint40', }, ], - stateMutability: "view", - type: "function", + stateMutability: 'view', + type: 'function', }, { inputs: [], - name: "migrator", + name: 'migrator', outputs: [ { - internalType: "address", - name: "", - type: "address", + internalType: 'address', + name: '', + type: 'address', }, ], - stateMutability: "view", - type: "function", + stateMutability: 'view', + type: 'function', }, { inputs: [], - name: "name", + name: 'name', outputs: [ { - internalType: "string", - name: "", - type: "string", + internalType: 'string', + name: '', + type: 'string', }, ], - stateMutability: "view", - type: "function", + stateMutability: 'view', + type: 'function', }, { inputs: [ { - internalType: "address", - name: "owner", - type: "address", + internalType: 'address', + name: 'owner', + type: 'address', }, ], - name: "nonces", + name: 'nonces', outputs: [ { - internalType: "uint256", - name: "", - type: "uint256", + internalType: 'uint256', + name: '', + type: 'uint256', }, ], - stateMutability: "view", - type: "function", + stateMutability: 'view', + type: 'function', }, { inputs: [], - name: "owner", + name: 'owner', outputs: [ { - internalType: "address", - name: "", - type: "address", + internalType: 'address', + name: '', + type: 'address', }, ], - stateMutability: "view", - type: "function", + stateMutability: 'view', + type: 'function', }, { inputs: [], - name: "pause", + name: 'pause', outputs: [], - stateMutability: "nonpayable", - type: "function", + stateMutability: 'nonpayable', + type: 'function', }, { inputs: [], - name: "paused", + name: 'paused', outputs: [ { - internalType: "bool", - name: "", - type: "bool", + internalType: 'bool', + name: '', + type: 'bool', }, ], - stateMutability: "view", - type: "function", + stateMutability: 'view', + type: 'function', }, { inputs: [], - name: "pendingOwner", + name: 'pendingOwner', outputs: [ { - internalType: "address", - name: "", - type: "address", + internalType: 'address', + name: '', + type: 'address', }, ], - stateMutability: "view", - type: "function", + stateMutability: 'view', + type: 'function', }, { inputs: [ { - internalType: "address", - name: "from", - type: "address", + internalType: 'address', + name: 'from', + type: 'address', }, { - internalType: "address", - name: "to", - type: "address", + internalType: 'address', + name: 'to', + type: 'address', }, { - internalType: "uint256", - name: "deadline", - type: "uint256", + internalType: 'uint256', + name: 'deadline', + type: 'uint256', }, { - internalType: "bytes", - name: "sig", - type: "bytes", + internalType: 'bytes', + name: 'sig', + type: 'bytes', }, ], - name: "recover", + name: 'recover', outputs: [], - stateMutability: "nonpayable", - type: "function", + stateMutability: 'nonpayable', + type: 'function', }, { inputs: [ { - internalType: "address", - name: "from", - type: "address", + internalType: 'address', + name: 'from', + type: 'address', }, { - internalType: "address", - name: "to", - type: "address", + internalType: 'address', + name: 'to', + type: 'address', }, { - internalType: "uint256", - name: "recoveryDeadline", - type: "uint256", + internalType: 'uint256', + name: 'recoveryDeadline', + type: 'uint256', }, { - internalType: "bytes", - name: "recoverySig", - type: "bytes", + internalType: 'bytes', + name: 'recoverySig', + type: 'bytes', }, { - internalType: "uint256", - name: "toDeadline", - type: "uint256", + internalType: 'uint256', + name: 'toDeadline', + type: 'uint256', }, { - internalType: "bytes", - name: "toSig", - type: "bytes", + internalType: 'bytes', + name: 'toSig', + type: 'bytes', }, ], - name: "recoverFor", + name: 'recoverFor', outputs: [], - stateMutability: "nonpayable", - type: "function", + stateMutability: 'nonpayable', + type: 'function', }, { inputs: [ { - internalType: "uint256", - name: "fid", - type: "uint256", + internalType: 'uint256', + name: 'fid', + type: 'uint256', }, ], - name: "recoveryOf", + name: 'recoveryOf', outputs: [ { - internalType: "address", - name: "recovery", - type: "address", + internalType: 'address', + name: 'recovery', + type: 'address', }, ], - stateMutability: "view", - type: "function", + stateMutability: 'view', + type: 'function', }, { inputs: [ { - internalType: "address", - name: "to", - type: "address", + internalType: 'address', + name: 'to', + type: 'address', }, { - internalType: "address", - name: "recovery", - type: "address", + internalType: 'address', + name: 'recovery', + type: 'address', }, ], - name: "register", + name: 'register', outputs: [ { - internalType: "uint256", - name: "fid", - type: "uint256", + internalType: 'uint256', + name: 'fid', + type: 'uint256', }, ], - stateMutability: "nonpayable", - type: "function", + stateMutability: 'nonpayable', + type: 'function', }, { inputs: [ { - internalType: "address", - name: "guardian", - type: "address", + internalType: 'address', + name: 'guardian', + type: 'address', }, ], - name: "removeGuardian", + name: 'removeGuardian', outputs: [], - stateMutability: "nonpayable", - type: "function", + stateMutability: 'nonpayable', + type: 'function', }, { inputs: [], - name: "renounceOwnership", + name: 'renounceOwnership', outputs: [], - stateMutability: "nonpayable", - type: "function", + stateMutability: 'nonpayable', + type: 'function', }, { inputs: [ { - internalType: "uint256", - name: "_counter", - type: "uint256", + internalType: 'uint256', + name: '_counter', + type: 'uint256', }, ], - name: "setIdCounter", + name: 'setIdCounter', outputs: [], - stateMutability: "nonpayable", - type: "function", + stateMutability: 'nonpayable', + type: 'function', }, { inputs: [ { - internalType: "address", - name: "_idGateway", - type: "address", + internalType: 'address', + name: '_idGateway', + type: 'address', }, ], - name: "setIdGateway", + name: 'setIdGateway', outputs: [], - stateMutability: "nonpayable", - type: "function", + stateMutability: 'nonpayable', + type: 'function', }, { inputs: [ { - internalType: "address", - name: "_migrator", - type: "address", + internalType: 'address', + name: '_migrator', + type: 'address', }, ], - name: "setMigrator", + name: 'setMigrator', outputs: [], - stateMutability: "nonpayable", - type: "function", + stateMutability: 'nonpayable', + type: 'function', }, { inputs: [ { - internalType: "address", - name: "to", - type: "address", + internalType: 'address', + name: 'to', + type: 'address', }, { - internalType: "uint256", - name: "deadline", - type: "uint256", + internalType: 'uint256', + name: 'deadline', + type: 'uint256', }, { - internalType: "bytes", - name: "sig", - type: "bytes", + internalType: 'bytes', + name: 'sig', + type: 'bytes', }, ], - name: "transfer", + name: 'transfer', outputs: [], - stateMutability: "nonpayable", - type: "function", + stateMutability: 'nonpayable', + type: 'function', }, { inputs: [ { - internalType: "address", - name: "to", - type: "address", + internalType: 'address', + name: 'to', + type: 'address', }, { - internalType: "address", - name: "recovery", - type: "address", + internalType: 'address', + name: 'recovery', + type: 'address', }, { - internalType: "uint256", - name: "deadline", - type: "uint256", + internalType: 'uint256', + name: 'deadline', + type: 'uint256', }, { - internalType: "bytes", - name: "sig", - type: "bytes", + internalType: 'bytes', + name: 'sig', + type: 'bytes', }, ], - name: "transferAndChangeRecovery", + name: 'transferAndChangeRecovery', outputs: [], - stateMutability: "nonpayable", - type: "function", + stateMutability: 'nonpayable', + type: 'function', }, { inputs: [ { - internalType: "address", - name: "from", - type: "address", + internalType: 'address', + name: 'from', + type: 'address', }, { - internalType: "address", - name: "to", - type: "address", + internalType: 'address', + name: 'to', + type: 'address', }, { - internalType: "address", - name: "recovery", - type: "address", + internalType: 'address', + name: 'recovery', + type: 'address', }, { - internalType: "uint256", - name: "fromDeadline", - type: "uint256", + internalType: 'uint256', + name: 'fromDeadline', + type: 'uint256', }, { - internalType: "bytes", - name: "fromSig", - type: "bytes", + internalType: 'bytes', + name: 'fromSig', + type: 'bytes', }, { - internalType: "uint256", - name: "toDeadline", - type: "uint256", + internalType: 'uint256', + name: 'toDeadline', + type: 'uint256', }, { - internalType: "bytes", - name: "toSig", - type: "bytes", + internalType: 'bytes', + name: 'toSig', + type: 'bytes', }, ], - name: "transferAndChangeRecoveryFor", + name: 'transferAndChangeRecoveryFor', outputs: [], - stateMutability: "nonpayable", - type: "function", + stateMutability: 'nonpayable', + type: 'function', }, { inputs: [ { - internalType: "address", - name: "from", - type: "address", + internalType: 'address', + name: 'from', + type: 'address', }, { - internalType: "address", - name: "to", - type: "address", + internalType: 'address', + name: 'to', + type: 'address', }, { - internalType: "uint256", - name: "fromDeadline", - type: "uint256", + internalType: 'uint256', + name: 'fromDeadline', + type: 'uint256', }, { - internalType: "bytes", - name: "fromSig", - type: "bytes", + internalType: 'bytes', + name: 'fromSig', + type: 'bytes', }, { - internalType: "uint256", - name: "toDeadline", - type: "uint256", + internalType: 'uint256', + name: 'toDeadline', + type: 'uint256', }, { - internalType: "bytes", - name: "toSig", - type: "bytes", + internalType: 'bytes', + name: 'toSig', + type: 'bytes', }, ], - name: "transferFor", + name: 'transferFor', outputs: [], - stateMutability: "nonpayable", - type: "function", + stateMutability: 'nonpayable', + type: 'function', }, { inputs: [ { - internalType: "address", - name: "newOwner", - type: "address", + internalType: 'address', + name: 'newOwner', + type: 'address', }, ], - name: "transferOwnership", + name: 'transferOwnership', outputs: [], - stateMutability: "nonpayable", - type: "function", + stateMutability: 'nonpayable', + type: 'function', }, { inputs: [], - name: "unpause", + name: 'unpause', outputs: [], - stateMutability: "nonpayable", - type: "function", + stateMutability: 'nonpayable', + type: 'function', }, { inputs: [], - name: "useNonce", + name: 'useNonce', outputs: [ { - internalType: "uint256", - name: "", - type: "uint256", + internalType: 'uint256', + name: '', + type: 'uint256', }, ], - stateMutability: "nonpayable", - type: "function", + stateMutability: 'nonpayable', + type: 'function', }, { inputs: [ { - internalType: "address", - name: "custodyAddress", - type: "address", + internalType: 'address', + name: 'custodyAddress', + type: 'address', }, { - internalType: "uint256", - name: "fid", - type: "uint256", + internalType: 'uint256', + name: 'fid', + type: 'uint256', }, { - internalType: "bytes32", - name: "digest", - type: "bytes32", + internalType: 'bytes32', + name: 'digest', + type: 'bytes32', }, { - internalType: "bytes", - name: "sig", - type: "bytes", + internalType: 'bytes', + name: 'sig', + type: 'bytes', }, ], - name: "verifyFidSignature", + name: 'verifyFidSignature', outputs: [ { - internalType: "bool", - name: "isValid", - type: "bool", + internalType: 'bool', + name: 'isValid', + type: 'bool', }, ], - stateMutability: "view", - type: "function", + stateMutability: 'view', + type: 'function', }, -] as const; +] as const -export const ID_REGISTRY_ADDRESS = "0x00000000Fc6c5F01Fc30151999387Bb99A9f489b" as const; +export const ID_REGISTRY_ADDRESS = + '0x00000000Fc6c5F01Fc30151999387Bb99A9f489b' as const diff --git a/packages/core/src/errors.ts b/packages/core/src/errors.ts index d8b5c31..c97c597 100644 --- a/packages/core/src/errors.ts +++ b/packages/core/src/errors.ts @@ -1,38 +1,41 @@ interface AuthClientErrorOpts { - message: string; - cause: Error | AuthClientError; - presentable: boolean; + message: string + cause: Error | AuthClientError + presentable: boolean } export class AuthClientError extends Error { - public readonly errCode: AuthClientErrorCode; + public readonly errCode: AuthClientErrorCode /* Indicates if error message can be presented to the user */ - public readonly presentable: boolean = false; + public readonly presentable: boolean = false /** * @param errCode - the AuthClientError code for this message * @param context - a message, another Error, or a AuthClientErrorOpts */ - constructor(errCode: AuthClientErrorCode, context: Partial | string | Error) { - let parsedContext: string | Error | Partial; + constructor( + errCode: AuthClientErrorCode, + context: Partial | string | Error, + ) { + let parsedContext: string | Error | Partial - if (typeof context === "string") { - parsedContext = { message: context }; + if (typeof context === 'string') { + parsedContext = { message: context } } else if (context instanceof Error) { - parsedContext = { cause: context, message: context.message }; + parsedContext = { cause: context, message: context.message } } else { - parsedContext = context; + parsedContext = context } if (!parsedContext.message) { - parsedContext.message = parsedContext.cause?.message || ""; + parsedContext.message = parsedContext.cause?.message || '' } - super(parsedContext.message, { cause: parsedContext.cause }); + super(parsedContext.message, { cause: parsedContext.cause }) - this.name = "AuthClientError"; - this.errCode = errCode; + this.name = 'AuthClientError' + this.errCode = errCode } } @@ -45,17 +48,17 @@ export class AuthClientError extends Error { */ export type AuthClientErrorCode = /* The request did not have valid authentication credentials, retry with credentials */ - | "unauthenticated" + | 'unauthenticated' /* The authenticated request did not have the authority to perform this action */ - | "unauthorized" + | 'unauthorized' /* The request cannot be completed as constructed, do not retry */ - | "bad_request" - | "bad_request.validation_failure" + | 'bad_request' + | 'bad_request.validation_failure' /* The requested resource could not be found */ - | "not_found" + | 'not_found' /* The request could not be completed because the operation is not executable */ - | "not_implemented" + | 'not_implemented' /* The request could not be completed, it may or may not be safe to retry */ - | "unavailable" + | 'unavailable' /* An unknown error was encountered */ - | "unknown"; + | 'unknown' diff --git a/packages/core/src/exports/actions.ts b/packages/core/src/exports/actions.ts index 449ecfa..8f83410 100644 --- a/packages/core/src/exports/actions.ts +++ b/packages/core/src/exports/actions.ts @@ -1,5 +1,5 @@ -export * from "../actions/app/createChannel.js"; -export * from "../actions/app/pollChannelTillCompleted.js"; -export * from "../actions/app/channel.js"; -export * from "../actions/app/verifySiweMessage.js"; -export * from "../actions/auth/authenticate.js"; +export * from '../actions/app/createChannel.js' +export * from '../actions/app/pollChannelTillCompleted.js' +export * from '../actions/app/channel.js' +export * from '../actions/app/verifySiweMessage.js' +export * from '../actions/auth/authenticate.js' diff --git a/packages/core/src/exports/clients.ts b/packages/core/src/exports/clients.ts index cf60f65..fd7c278 100644 --- a/packages/core/src/exports/clients.ts +++ b/packages/core/src/exports/clients.ts @@ -1 +1 @@ -export * from "../clients/index.js"; +export * from '../clients/index.js' diff --git a/packages/core/src/exports/contracts.ts b/packages/core/src/exports/contracts.ts index 1cd506e..6d34d20 100644 --- a/packages/core/src/exports/contracts.ts +++ b/packages/core/src/exports/contracts.ts @@ -1 +1 @@ -export * from "../contracts/idRegistry.js"; +export * from '../contracts/idRegistry.js' diff --git a/packages/core/src/exports/errors.ts b/packages/core/src/exports/errors.ts index 2b2c82d..80a3c4c 100644 --- a/packages/core/src/exports/errors.ts +++ b/packages/core/src/exports/errors.ts @@ -1 +1 @@ -export * from "../errors.js"; +export * from '../errors.js' diff --git a/packages/core/src/exports/index.ts b/packages/core/src/exports/index.ts index 40c05a4..7380d06 100644 --- a/packages/core/src/exports/index.ts +++ b/packages/core/src/exports/index.ts @@ -1,5 +1,5 @@ -export * from "./actions.js"; -export * from "./clients.js"; -export * from "./contracts.js"; -export * from "./errors.js"; -export * from "./utils.js"; +export * from './actions.js' +export * from './clients.js' +export * from './contracts.js' +export * from './errors.js' +export * from './utils.js' diff --git a/packages/core/src/exports/utils.ts b/packages/core/src/exports/utils.ts index 78d3638..a71697f 100644 --- a/packages/core/src/exports/utils.ts +++ b/packages/core/src/exports/utils.ts @@ -1,11 +1,11 @@ -export * from "../utils/constants.js"; -export * from "../utils/createSiweMessage.js"; -export * from "../utils/createSiweMessageFromSignInURI.js"; -export * from "../utils/getFidFromSiweMessage.js"; -export * from "../utils/getResourcesFromSiweMessage.js"; -export * from "../utils/getSiweMessage.js"; -export * from "../utils/getSiweMessageFromSignInURI.js"; -export * from "../utils/validateSiweMessageChainId.js"; -export * from "../utils/validateSiweMessageResources.js"; -export * from "../utils/validateSiweMessageStatement.js"; -export * from "../utils/verifySiweMessageWithVerifier.js"; +export * from '../utils/constants.js' +export * from '../utils/createSiweMessage.js' +export * from '../utils/createSiweMessageFromSignInURI.js' +export * from '../utils/getFidFromSiweMessage.js' +export * from '../utils/getResourcesFromSiweMessage.js' +export * from '../utils/getSiweMessage.js' +export * from '../utils/getSiweMessageFromSignInURI.js' +export * from '../utils/validateSiweMessageChainId.js' +export * from '../utils/validateSiweMessageResources.js' +export * from '../utils/validateSiweMessageStatement.js' +export * from '../utils/verifySiweMessageWithVerifier.js' diff --git a/packages/core/src/types/address.ts b/packages/core/src/types/address.ts index f64ddbb..816b1b8 100644 --- a/packages/core/src/types/address.ts +++ b/packages/core/src/types/address.ts @@ -1 +1 @@ -export type Address = `0x${string}`; +export type Address = `0x${string}` diff --git a/packages/core/src/types/utils.ts b/packages/core/src/types/utils.ts index 05c4c6c..1a7fd2e 100644 --- a/packages/core/src/types/utils.ts +++ b/packages/core/src/types/utils.ts @@ -1,7 +1,7 @@ // Copied from https://github.com/wevm/wagmi/blob/main/packages/core/src/types/utils.ts /** Combines members of an intersection into a readable type. */ // https://twitter.com/mattpocockuk/status/1622730173446557697?s=20&t=NdpAcmEFXY01xkqU3KO0Mg -export type Evaluate = { [key in keyof type]: type[key] } & unknown; +export type Evaluate = { [key in keyof type]: type[key] } & unknown /** * Makes all properties of an object optional. @@ -9,8 +9,11 @@ export type Evaluate = { [key in keyof type]: type[key] } & unknown; * Compatible with [`exactOptionalPropertyTypes`](https://www.typescriptlang.org/tsconfig#exactOptionalPropertyTypes). */ export type ExactPartial = { - [key in keyof type]?: type[key] | undefined; -}; + [key in keyof type]?: type[key] | undefined +} /** Strict version of built-in Omit type */ -export type Omit = Pick>; +export type Omit = Pick< + type, + Exclude +> diff --git a/packages/core/src/utils/constants.ts b/packages/core/src/utils/constants.ts index 0e36174..4e8ff73 100644 --- a/packages/core/src/utils/constants.ts +++ b/packages/core/src/utils/constants.ts @@ -1,3 +1,3 @@ -export const STATEMENT = "Farcaster Auth"; -export const CHAIN_ID = 10; -export const FID_URI_REGEX = /^farcaster:\/\/fid\/([1-9]\d*)\/?$/; +export const STATEMENT = 'Farcaster Auth' +export const CHAIN_ID = 10 +export const FID_URI_REGEX = /^farcaster:\/\/fid\/([1-9]\d*)\/?$/ diff --git a/packages/core/src/utils/createSiweMessage.ts b/packages/core/src/utils/createSiweMessage.ts index d530b99..0eef240 100644 --- a/packages/core/src/utils/createSiweMessage.ts +++ b/packages/core/src/utils/createSiweMessage.ts @@ -1,25 +1,28 @@ -import { SiweMessage } from "siwe"; -import { AuthClientError } from "../errors.js"; -import { STATEMENT, CHAIN_ID } from "./constants.js"; -import { getSiweMessage } from "./getSiweMessage.js"; +import type { SiweMessage } from 'siwe' +import { AuthClientError } from '../errors.js' +import { CHAIN_ID, STATEMENT } from './constants.js' +import { getSiweMessage } from './getSiweMessage.js' -export type FarcasterResourceParameters = { fid: number }; -export type CreateSiweMessageParameters = Partial & FarcasterResourceParameters; +export type FarcasterResourceParameters = { fid: number } +export type CreateSiweMessageParameters = Partial & + FarcasterResourceParameters -export const createSiweMessage = (parameters: CreateSiweMessageParameters): SiweMessage => { +export const createSiweMessage = ( + parameters: CreateSiweMessageParameters, +): SiweMessage => { try { - const { fid, ...siweParameters } = parameters; - const resources = siweParameters.resources ?? []; - siweParameters.version = "1"; - siweParameters.statement = STATEMENT; - siweParameters.chainId = CHAIN_ID; - siweParameters.resources = [createFidResource(fid), ...resources]; - return getSiweMessage({ message: siweParameters }); + const { fid, ...siweParameters } = parameters + const resources = siweParameters.resources ?? [] + siweParameters.version = '1' + siweParameters.statement = STATEMENT + siweParameters.chainId = CHAIN_ID + siweParameters.resources = [createFidResource(fid), ...resources] + return getSiweMessage({ message: siweParameters }) } catch (e) { - throw new AuthClientError("bad_request.validation_failure", e as Error); + throw new AuthClientError('bad_request.validation_failure', e as Error) } -}; +} const createFidResource = (fid: number): string => { - return `farcaster://fid/${fid}`; -}; + return `farcaster://fid/${fid}` +} diff --git a/packages/core/src/utils/createSiweMessageFromSignInURI.ts b/packages/core/src/utils/createSiweMessageFromSignInURI.ts index e553284..89408af 100644 --- a/packages/core/src/utils/createSiweMessageFromSignInURI.ts +++ b/packages/core/src/utils/createSiweMessageFromSignInURI.ts @@ -1,10 +1,16 @@ -import { SiweMessage } from "siwe"; -import { createSiweMessage } from "./createSiweMessage.js"; -import { getSiweMessageFromSignInURI } from "./getSiweMessageFromSignInURI.js"; +import type { SiweMessage } from 'siwe' +import { createSiweMessage } from './createSiweMessage.js' +import { getSiweMessageFromSignInURI } from './getSiweMessageFromSignInURI.js' -export type CreateSiweMessageFromSignInURIParameters = { uri: string; fid: number }; +export type CreateSiweMessageFromSignInURIParameters = { + uri: string + fid: number +} -export const createSiweMessageFromSignInURI = ({ uri, fid }: CreateSiweMessageFromSignInURIParameters): SiweMessage => { - const parsedSignInURI = getSiweMessageFromSignInURI({ uri }); - return createSiweMessage({ ...parsedSignInURI.params, fid }); -}; +export const createSiweMessageFromSignInURI = ({ + uri, + fid, +}: CreateSiweMessageFromSignInURIParameters): SiweMessage => { + const parsedSignInURI = getSiweMessageFromSignInURI({ uri }) + return createSiweMessage({ ...parsedSignInURI.params, fid }) +} diff --git a/packages/core/src/utils/getFidFromSiweMessage.ts b/packages/core/src/utils/getFidFromSiweMessage.ts index 2ad9abd..c8b2d79 100644 --- a/packages/core/src/utils/getFidFromSiweMessage.ts +++ b/packages/core/src/utils/getFidFromSiweMessage.ts @@ -1,18 +1,23 @@ -import { SiweMessage } from "siwe"; -import { AuthClientError } from "../errors.js"; -import { FID_URI_REGEX } from "./constants.js"; +import type { SiweMessage } from 'siwe' +import { AuthClientError } from '../errors.js' +import { FID_URI_REGEX } from './constants.js' -export type GetFidFromSiweMessageParameters = { message: SiweMessage }; -export const getFidFromSiweMessage = ({ message }: GetFidFromSiweMessageParameters): number => { +export type GetFidFromSiweMessageParameters = { message: SiweMessage } +export const getFidFromSiweMessage = ({ + message, +}: GetFidFromSiweMessageParameters): number => { const resource = (message.resources ?? []).find((resource) => { - return FID_URI_REGEX.test(resource); - }); + return FID_URI_REGEX.test(resource) + }) if (!resource) { - throw new AuthClientError("bad_request.validation_failure", "No fid resource provided"); + throw new AuthClientError( + 'bad_request.validation_failure', + 'No fid resource provided', + ) } - const fid = parseInt(resource.match(FID_URI_REGEX)?.[1] ?? ""); + const fid = Number.parseInt(resource.match(FID_URI_REGEX)?.[1] ?? '') if (Number.isNaN(fid)) { - throw new AuthClientError("bad_request.validation_failure", "Invalid fid"); + throw new AuthClientError('bad_request.validation_failure', 'Invalid fid') } - return fid; -}; + return fid +} diff --git a/packages/core/src/utils/getResourcesFromSiweMessage.ts b/packages/core/src/utils/getResourcesFromSiweMessage.ts index 9961d83..f947207 100644 --- a/packages/core/src/utils/getResourcesFromSiweMessage.ts +++ b/packages/core/src/utils/getResourcesFromSiweMessage.ts @@ -1,10 +1,10 @@ -import { SiweMessage } from "siwe"; -import { type FarcasterResourceParameters } from "./createSiweMessage.js"; -import { getFidFromSiweMessage } from "./getFidFromSiweMessage.js"; +import type { SiweMessage } from 'siwe' +import type { FarcasterResourceParameters } from './createSiweMessage.js' +import { getFidFromSiweMessage } from './getFidFromSiweMessage.js' -export type GetResourcesFromSiweMessageParameters = { message: SiweMessage }; +export type GetResourcesFromSiweMessageParameters = { message: SiweMessage } export const getResourcesFromSiweMessage = ({ message, }: GetResourcesFromSiweMessageParameters): FarcasterResourceParameters => { - return { fid: getFidFromSiweMessage({ message }) }; -}; + return { fid: getFidFromSiweMessage({ message }) } +} diff --git a/packages/core/src/utils/getSiweMessage.test.ts b/packages/core/src/utils/getSiweMessage.test.ts index 02d1528..7735a00 100644 --- a/packages/core/src/utils/getSiweMessage.test.ts +++ b/packages/core/src/utils/getSiweMessage.test.ts @@ -1,121 +1,141 @@ -import { getSiweMessage } from "./getSiweMessage.js"; -import { AuthClientError } from "../errors.js"; +import { AuthClientError } from '../errors.js' +import { getSiweMessage } from './getSiweMessage.js' const siweParams = { - domain: "example.com", - address: "0x63C378DDC446DFf1d831B9B96F7d338FE6bd4231", - uri: "https://example.com/login", - version: "1", - nonce: "12345678", - issuedAt: "2023-10-01T00:00:00.000Z", -}; + domain: 'example.com', + address: '0x63C378DDC446DFf1d831B9B96F7d338FE6bd4231', + uri: 'https://example.com/login', + version: '1', + nonce: '12345678', + issuedAt: '2023-10-01T00:00:00.000Z', +} const authParams = { ...siweParams, - statement: "Farcaster Auth", + statement: 'Farcaster Auth', chainId: 10, - resources: ["farcaster://fid/1234"], -}; + resources: ['farcaster://fid/1234'], +} -describe("getSiweMessage", () => { - test("default parameters are valid", () => { - expect(() => getSiweMessage({ message: authParams })).not.toThrow(); - }); +describe('getSiweMessage', () => { + test('default parameters are valid', () => { + expect(() => getSiweMessage({ message: authParams })).not.toThrow() + }) - test("propagates SIWE message errors", () => { + test('propagates SIWE message errors', () => { try { getSiweMessage({ message: { ...authParams, - address: "Invalid address", + address: 'Invalid address', }, - }); - expect(true).toBe(false); + }) + expect(true).toBe(false) } catch (e) { - expect(e).toBeInstanceOf(AuthClientError); - if (!(e instanceof AuthClientError)) throw new Error("unexpected"); + expect(e).toBeInstanceOf(AuthClientError) + if (!(e instanceof AuthClientError)) throw new Error('unexpected') - expect(e.errCode).toEqual("bad_request.validation_failure"); - expect(e.message).toMatch("invalid address"); + expect(e.errCode).toEqual('bad_request.validation_failure') + expect(e.message).toMatch('invalid address') } - }); + }) test("message must contain 'Farcaster Auth'", () => { try { getSiweMessage({ message: { ...authParams, - statement: "Invalid statement", + statement: 'Invalid statement', }, - }); - expect(true).toBe(false); + }) + expect(true).toBe(false) } catch (e) { - expect(e).toBeInstanceOf(AuthClientError); - if (!(e instanceof AuthClientError)) throw new Error("unexpected"); + expect(e).toBeInstanceOf(AuthClientError) + if (!(e instanceof AuthClientError)) throw new Error('unexpected') - expect(e).toEqual(new AuthClientError("bad_request.validation_failure", "Statement must be 'Farcaster Auth'")); + expect(e).toEqual( + new AuthClientError( + 'bad_request.validation_failure', + "Statement must be 'Farcaster Auth'", + ), + ) } - }); + }) test("statement allows 'Farcaster Connect'", () => { expect(() => getSiweMessage({ message: { ...authParams, - statement: "Farcaster Connect", + statement: 'Farcaster Connect', }, }), - ).not.toThrow(); - }); + ).not.toThrow() + }) - test("message must include chainId 10", () => { + test('message must include chainId 10', () => { try { getSiweMessage({ message: { ...authParams, chainId: 1, }, - }); - expect(true).toBe(false); + }) + expect(true).toBe(false) } catch (e) { - expect(e).toBeInstanceOf(AuthClientError); - if (!(e instanceof AuthClientError)) throw new Error("unexpected"); + expect(e).toBeInstanceOf(AuthClientError) + if (!(e instanceof AuthClientError)) throw new Error('unexpected') - expect(e).toEqual(new AuthClientError("bad_request.validation_failure", "Chain ID must be 10")); + expect(e).toEqual( + new AuthClientError( + 'bad_request.validation_failure', + 'Chain ID must be 10', + ), + ) } - }); + }) - test("message must include FID resource", () => { + test('message must include FID resource', () => { try { getSiweMessage({ message: { ...authParams, resources: [], }, - }); - expect(true).toBe(false); + }) + expect(true).toBe(false) } catch (e) { - expect(e).toBeInstanceOf(AuthClientError); - if (!(e instanceof AuthClientError)) throw new Error("unexpected"); + expect(e).toBeInstanceOf(AuthClientError) + if (!(e instanceof AuthClientError)) throw new Error('unexpected') - expect(e).toEqual(new AuthClientError("bad_request.validation_failure", "No fid resource provided")); + expect(e).toEqual( + new AuthClientError( + 'bad_request.validation_failure', + 'No fid resource provided', + ), + ) } - }); + }) - test("message must only include one FID resource", () => { + test('message must only include one FID resource', () => { try { getSiweMessage({ message: { ...authParams, - resources: ["farcaster://fid/1", "farcaster://fid/2"], + resources: ['farcaster://fid/1', 'farcaster://fid/2'], }, - }); - expect(true).toBe(false); + }) + expect(true).toBe(false) } catch (e) { - expect(e).toBeInstanceOf(AuthClientError); - if (!(e instanceof AuthClientError)) throw new Error("unexpected"); + expect(e).toBeInstanceOf(AuthClientError) + if (!(e instanceof AuthClientError)) throw new Error('unexpected') - expect(e).toEqual(new AuthClientError("bad_request.validation_failure", "Multiple fid resources provided")); + expect(e).toEqual( + new AuthClientError( + 'bad_request.validation_failure', + 'Multiple fid resources provided', + ), + ) } - }); -}); + }) +}) diff --git a/packages/core/src/utils/getSiweMessage.ts b/packages/core/src/utils/getSiweMessage.ts index 0424ded..853cdea 100644 --- a/packages/core/src/utils/getSiweMessage.ts +++ b/packages/core/src/utils/getSiweMessage.ts @@ -1,18 +1,22 @@ -import { SiweMessage } from "siwe"; -import { AuthClientError } from "../errors.js"; -import { validateSiweMessageChainId } from "./validateSiweMessageChainId.js"; -import { validateSiweMessageResources } from "./validateSiweMessageResources.js"; -import { validateSiweMessageStatement } from "./validateSiweMessageStatement.js"; +import { SiweMessage } from 'siwe' +import { AuthClientError } from '../errors.js' +import { validateSiweMessageChainId } from './validateSiweMessageChainId.js' +import { validateSiweMessageResources } from './validateSiweMessageResources.js' +import { validateSiweMessageStatement } from './validateSiweMessageStatement.js' -export type GetSiweMessageParameters = { message: string | Partial }; -export const getSiweMessage = (parameters: GetSiweMessageParameters): SiweMessage => { +export type GetSiweMessageParameters = { + message: string | Partial +} +export const getSiweMessage = ( + parameters: GetSiweMessageParameters, +): SiweMessage => { try { - const message = new SiweMessage(parameters.message); - validateSiweMessageStatement({ message }); - validateSiweMessageChainId({ message }); - validateSiweMessageResources({ message }); - return message; + const message = new SiweMessage(parameters.message) + validateSiweMessageStatement({ message }) + validateSiweMessageChainId({ message }) + validateSiweMessageResources({ message }) + return message } catch (e) { - throw new AuthClientError("bad_request.validation_failure", e as Error); + throw new AuthClientError('bad_request.validation_failure', e as Error) } -}; +} diff --git a/packages/core/src/utils/getSiweMessageFromSignInURI.test.ts b/packages/core/src/utils/getSiweMessageFromSignInURI.test.ts index 7b10746..065fe12 100644 --- a/packages/core/src/utils/getSiweMessageFromSignInURI.test.ts +++ b/packages/core/src/utils/getSiweMessageFromSignInURI.test.ts @@ -1,16 +1,16 @@ -import { getSiweMessageFromSignInURI } from "./getSiweMessageFromSignInURI.js"; +import { getSiweMessageFromSignInURI } from './getSiweMessageFromSignInURI.js' -describe("getSiweMessageFromSignInURI", () => { - test("parses sign in params from protocol URI", async () => { +describe('getSiweMessageFromSignInURI', () => { + test('parses sign in params from protocol URI', async () => { const { channelToken, params } = getSiweMessageFromSignInURI({ - uri: "https://warpcast.com/~/sign-in-with-farcaster?channelToken=76be6229-bdf7-4ad2-930a-540fb2de1e08&nonce=ESsxs6MaFio7OvqWb&siweUri=https%3A%2F%2Fexample.com%2Flogin&domain=example.com", - }); + uri: 'https://warpcast.com/~/sign-in-with-farcaster?channelToken=76be6229-bdf7-4ad2-930a-540fb2de1e08&nonce=ESsxs6MaFio7OvqWb&siweUri=https%3A%2F%2Fexample.com%2Flogin&domain=example.com', + }) - expect(channelToken).toBe("76be6229-bdf7-4ad2-930a-540fb2de1e08"); + expect(channelToken).toBe('76be6229-bdf7-4ad2-930a-540fb2de1e08') expect(params).toStrictEqual({ - domain: "example.com", - uri: "https://example.com/login", - nonce: "ESsxs6MaFio7OvqWb", - }); - }); -}); + domain: 'example.com', + uri: 'https://example.com/login', + nonce: 'ESsxs6MaFio7OvqWb', + }) + }) +}) diff --git a/packages/core/src/utils/getSiweMessageFromSignInURI.ts b/packages/core/src/utils/getSiweMessageFromSignInURI.ts index d32f58e..c82c47f 100644 --- a/packages/core/src/utils/getSiweMessageFromSignInURI.ts +++ b/packages/core/src/utils/getSiweMessageFromSignInURI.ts @@ -1,35 +1,35 @@ -import { AuthClientError } from "../errors.js"; -import { type CreateSiweMessageParameters } from "./createSiweMessage.js"; +import { AuthClientError } from '../errors.js' +import type { CreateSiweMessageParameters } from './createSiweMessage.js' -export type GetSiweMessageFromSignInURIParameters = { uri: string }; +export type GetSiweMessageFromSignInURIParameters = { uri: string } export type GetSiweMessageFromSignInURIReturnType = { - channelToken: string; - params: Partial; -}; + channelToken: string + params: Partial +} export const getSiweMessageFromSignInURI = ({ uri, }: GetSiweMessageFromSignInURIParameters): GetSiweMessageFromSignInURIReturnType => { - const url = new URL(uri); - const searchParams = Object.fromEntries(url.searchParams.entries()); - const { channelToken, ...params } = searchParams; + const url = new URL(uri) + const searchParams = Object.fromEntries(url.searchParams.entries()) + const { channelToken, ...params } = searchParams if (!channelToken) { - throw validationFail("No channel token provided"); + throw validationFail('No channel token provided') } - if (!params["nonce"]) { - throw validationFail("No nonce provided"); + if (!params.nonce) { + throw validationFail('No nonce provided') } - if (!params["siweUri"]) { - throw validationFail("No SIWE URI provided"); + if (!params.siweUri) { + throw validationFail('No SIWE URI provided') } - if (!params["domain"]) { - throw validationFail("No domain provided"); + if (!params.domain) { + throw validationFail('No domain provided') } - const { siweUri, ...siweParameters } = params; - return { channelToken, params: { uri: siweUri, ...siweParameters } }; -}; + const { siweUri, ...siweParameters } = params + return { channelToken, params: { uri: siweUri, ...siweParameters } } +} const validationFail = (message: string): AuthClientError => { - return new AuthClientError("bad_request.validation_failure", message); -}; + return new AuthClientError('bad_request.validation_failure', message) +} diff --git a/packages/core/src/utils/validateSiweMessageChainId.ts b/packages/core/src/utils/validateSiweMessageChainId.ts index 51ed96b..dd54847 100644 --- a/packages/core/src/utils/validateSiweMessageChainId.ts +++ b/packages/core/src/utils/validateSiweMessageChainId.ts @@ -1,10 +1,15 @@ -import { SiweMessage } from "siwe"; -import { AuthClientError } from "../errors.js"; -import { CHAIN_ID } from "./constants.js"; +import type { SiweMessage } from 'siwe' +import { AuthClientError } from '../errors.js' +import { CHAIN_ID } from './constants.js' -type ValidateSiweMessageChainIdParameters = { message: SiweMessage }; -export const validateSiweMessageChainId = ({ message }: ValidateSiweMessageChainIdParameters): void => { +type ValidateSiweMessageChainIdParameters = { message: SiweMessage } +export const validateSiweMessageChainId = ({ + message, +}: ValidateSiweMessageChainIdParameters): void => { if (message.chainId !== CHAIN_ID) { - throw new AuthClientError("bad_request.validation_failure", `Chain ID must be ${CHAIN_ID}`); + throw new AuthClientError( + 'bad_request.validation_failure', + `Chain ID must be ${CHAIN_ID}`, + ) } -}; +} diff --git a/packages/core/src/utils/validateSiweMessageResources.ts b/packages/core/src/utils/validateSiweMessageResources.ts index e26d22c..dd6e6b7 100644 --- a/packages/core/src/utils/validateSiweMessageResources.ts +++ b/packages/core/src/utils/validateSiweMessageResources.ts @@ -1,16 +1,24 @@ -import { SiweMessage } from "siwe"; -import { AuthClientError } from "../errors.js"; -import { FID_URI_REGEX } from "./constants.js"; +import type { SiweMessage } from 'siwe' +import { AuthClientError } from '../errors.js' +import { FID_URI_REGEX } from './constants.js' -type ValidateSiweMessageResourcesParameters = { message: SiweMessage }; -export const validateSiweMessageResources = ({ message }: ValidateSiweMessageResourcesParameters): void => { +type ValidateSiweMessageResourcesParameters = { message: SiweMessage } +export const validateSiweMessageResources = ({ + message, +}: ValidateSiweMessageResourcesParameters): void => { const fidResources = (message.resources ?? []).filter((resource) => { - return FID_URI_REGEX.test(resource); - }); + return FID_URI_REGEX.test(resource) + }) if (fidResources.length === 0) { - throw new AuthClientError("bad_request.validation_failure", "No fid resource provided"); + throw new AuthClientError( + 'bad_request.validation_failure', + 'No fid resource provided', + ) } if (fidResources.length > 1) { - throw new AuthClientError("bad_request.validation_failure", "Multiple fid resources provided"); + throw new AuthClientError( + 'bad_request.validation_failure', + 'Multiple fid resources provided', + ) } -}; +} diff --git a/packages/core/src/utils/validateSiweMessageStatement.ts b/packages/core/src/utils/validateSiweMessageStatement.ts index 18d215d..6201895 100644 --- a/packages/core/src/utils/validateSiweMessageStatement.ts +++ b/packages/core/src/utils/validateSiweMessageStatement.ts @@ -1,12 +1,18 @@ -import { SiweMessage } from "siwe"; -import { AuthClientError } from "../errors.js"; -import { STATEMENT } from "./constants.js"; +import type { SiweMessage } from 'siwe' +import { AuthClientError } from '../errors.js' +import { STATEMENT } from './constants.js' -export type ValidateSiweMessageStatementParameters = { message: SiweMessage }; +export type ValidateSiweMessageStatementParameters = { message: SiweMessage } -export const validateSiweMessageStatement = ({ message }: ValidateSiweMessageStatementParameters): void => { - const validStatement = message.statement === STATEMENT || message.statement === "Farcaster Connect"; +export const validateSiweMessageStatement = ({ + message, +}: ValidateSiweMessageStatementParameters): void => { + const validStatement = + message.statement === STATEMENT || message.statement === 'Farcaster Connect' if (!validStatement) { - throw new AuthClientError("bad_request.validation_failure", `Statement must be '${STATEMENT}'`); + throw new AuthClientError( + 'bad_request.validation_failure', + `Statement must be '${STATEMENT}'`, + ) } -}; +} diff --git a/packages/core/src/utils/verifySiweMessageWithVerifier.test.ts b/packages/core/src/utils/verifySiweMessageWithVerifier.test.ts index 20d54c4..56090f0 100644 --- a/packages/core/src/utils/verifySiweMessageWithVerifier.test.ts +++ b/packages/core/src/utils/verifySiweMessageWithVerifier.test.ts @@ -1,242 +1,280 @@ -import { verifySiweMessageWithVerifier } from "./verifySiweMessageWithVerifier.js"; -import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; -import { type Hex, zeroAddress } from "viem"; -import { getDefaultProvider } from "ethers"; -import { AuthClientError } from "../errors.js"; -import { createSiweMessage } from "./createSiweMessage.js"; +import { getDefaultProvider } from 'ethers' +import { type Hex, zeroAddress } from 'viem' +import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts' +import { AuthClientError } from '../errors.js' +import { createSiweMessage } from './createSiweMessage.js' +import { verifySiweMessageWithVerifier } from './verifySiweMessageWithVerifier.js' -const account = privateKeyToAccount(generatePrivateKey()); +const account = privateKeyToAccount(generatePrivateKey()) const siweParams = { - domain: "example.com", - address: "0x63C378DDC446DFf1d831B9B96F7d338FE6bd4231", - uri: "https://example.com/login", - version: "1", - nonce: "12345678", - issuedAt: "2023-10-01T00:00:00.000Z", -}; -const { nonce, domain } = siweParams; - -describe("verify", () => { - test("verifies valid EOA signatures", async () => { - const getFid = (_custody: Hex) => Promise.resolve(1234n); + domain: 'example.com', + address: '0x63C378DDC446DFf1d831B9B96F7d338FE6bd4231', + uri: 'https://example.com/login', + version: '1', + nonce: '12345678', + issuedAt: '2023-10-01T00:00:00.000Z', +} +const { nonce, domain } = siweParams + +describe('verify', () => { + test('verifies valid EOA signatures', async () => { + const getFid = (_custody: Hex) => Promise.resolve(1234n) const res = createSiweMessage({ ...siweParams, address: account.address, fid: 1234, - }); - const siweMessage = res; - const message = siweMessage.toMessage(); - const signature = await account.signMessage({ message }); - const result = await verifySiweMessageWithVerifier({ nonce, domain, message, signature, verifier: { getFid } }); + }) + const siweMessage = res + const message = siweMessage.toMessage() + const signature = await account.signMessage({ message }) + const result = await verifySiweMessageWithVerifier({ + nonce, + domain, + message, + signature, + verifier: { getFid }, + }) expect(result).toEqual({ data: siweMessage, success: true, fid: 1234, - }); - }); + }) + }) - test("adds parsed resources to response", async () => { - const getFid = (_custody: Hex) => Promise.resolve(1234n); + test('adds parsed resources to response', async () => { + const getFid = (_custody: Hex) => Promise.resolve(1234n) const res = createSiweMessage({ ...siweParams, address: account.address, fid: 1234, - }); - const siweMessage = res; - const message = siweMessage.toMessage(); - const signature = await account.signMessage({ message }); - const result = await verifySiweMessageWithVerifier({ nonce, domain, message, signature, verifier: { getFid } }); + }) + const siweMessage = res + const message = siweMessage.toMessage() + const signature = await account.signMessage({ message }) + const result = await verifySiweMessageWithVerifier({ + nonce, + domain, + message, + signature, + verifier: { getFid }, + }) expect(result).toEqual({ data: siweMessage, success: true, fid: 1234, - }); - }); + }) + }) - test("verifies valid 1271 signatures", async () => { - const getFid = (_custody: Hex) => Promise.resolve(1234n); - const provider = getDefaultProvider("https://mainnet.optimism.io"); + test('verifies valid 1271 signatures', async () => { + const getFid = (_custody: Hex) => Promise.resolve(1234n) + const provider = getDefaultProvider('https://mainnet.optimism.io') const res = createSiweMessage({ ...siweParams, - address: "0xC89858205c6AdDAD842E1F58eD6c42452671885A", + address: '0xC89858205c6AdDAD842E1F58eD6c42452671885A', fid: 1234, - }); - const siweMessage = res; - const message = siweMessage.toMessage(); - const signature = await account.signMessage({ message }); + }) + const siweMessage = res + const message = siweMessage.toMessage() + const signature = await account.signMessage({ message }) const result = await verifySiweMessageWithVerifier({ nonce, domain, message, signature, verifier: { getFid, provider }, - }); + }) expect(result).toEqual({ data: siweMessage, success: true, fid: 1234, - }); - }); + }) + }) - test("1271 signatures fail without provider", async () => { - const getFid = (_custody: Hex) => Promise.resolve(1234n); + test('1271 signatures fail without provider', async () => { + const getFid = (_custody: Hex) => Promise.resolve(1234n) const res = createSiweMessage({ ...siweParams, - address: "0xC89858205c6AdDAD842E1F58eD6c42452671885A", + address: '0xC89858205c6AdDAD842E1F58eD6c42452671885A', fid: 1234, - }); - const siweMessage = res; - const message = siweMessage.toMessage(); - const signature = await account.signMessage({ message }); + }) + const siweMessage = res + const message = siweMessage.toMessage() + const signature = await account.signMessage({ message }) try { - await verifySiweMessageWithVerifier({ nonce, domain, message, signature, verifier: { getFid } }); - expect(true).toBe(false); + await verifySiweMessageWithVerifier({ + nonce, + domain, + message, + signature, + verifier: { getFid }, + }) + expect(true).toBe(false) } catch (e) { - expect(e).toBeInstanceOf(AuthClientError); - if (!(e instanceof AuthClientError)) throw new Error("unexpected"); + expect(e).toBeInstanceOf(AuthClientError) + if (!(e instanceof AuthClientError)) throw new Error('unexpected') - expect(e.errCode).toBe("unauthorized"); - expect(e.message).toBe("Signature does not match address of the message."); + expect(e.errCode).toBe('unauthorized') + expect(e.message).toBe('Signature does not match address of the message.') } - }); + }) - test("invalid SIWE message", async () => { - const getFid = (_custody: Hex) => Promise.resolve(1234n); + test('invalid SIWE message', async () => { + const getFid = (_custody: Hex) => Promise.resolve(1234n) const res = createSiweMessage({ ...siweParams, address: zeroAddress, fid: 1234, - }); - const siweMessage = res; - const message = siweMessage.toMessage(); + }) + const siweMessage = res + const message = siweMessage.toMessage() const signature = await account.signMessage({ message, - }); + }) try { - await verifySiweMessageWithVerifier({ nonce, domain, message, signature, verifier: { getFid } }); - expect(true).toBe(false); + await verifySiweMessageWithVerifier({ + nonce, + domain, + message, + signature, + verifier: { getFid }, + }) + expect(true).toBe(false) } catch (e) { - expect(e).toBeInstanceOf(AuthClientError); - if (!(e instanceof AuthClientError)) throw new Error("unexpected"); + expect(e).toBeInstanceOf(AuthClientError) + if (!(e instanceof AuthClientError)) throw new Error('unexpected') - expect(e.errCode).toBe("unauthorized"); - expect(e.message).toBe("Signature does not match address of the message."); + expect(e.errCode).toBe('unauthorized') + expect(e.message).toBe('Signature does not match address of the message.') } - }); + }) - test("invalid fid owner", async () => { - const getFid = (_custody: Hex) => Promise.resolve(5678n); + test('invalid fid owner', async () => { + const getFid = (_custody: Hex) => Promise.resolve(5678n) const res = createSiweMessage({ ...siweParams, address: account.address, fid: 1234, - }); - const siweMessage = res; - const message = siweMessage.toMessage(); + }) + const siweMessage = res + const message = siweMessage.toMessage() const signature = await account.signMessage({ message, - }); + }) try { - await verifySiweMessageWithVerifier({ nonce, domain, message, signature, verifier: { getFid } }); - expect(true).toBe(false); + await verifySiweMessageWithVerifier({ + nonce, + domain, + message, + signature, + verifier: { getFid }, + }) + expect(true).toBe(false) } catch (e) { - expect(e).toBeInstanceOf(AuthClientError); - if (!(e instanceof AuthClientError)) throw new Error("unexpected"); + expect(e).toBeInstanceOf(AuthClientError) + if (!(e instanceof AuthClientError)) throw new Error('unexpected') - expect(e.errCode).toBe("unauthorized"); - expect(e.message).toBe(`Invalid resource: signer ${account.address} does not own fid 1234.`); + expect(e.errCode).toBe('unauthorized') + expect(e.message).toBe( + `Invalid resource: signer ${account.address} does not own fid 1234.`, + ) } - }); + }) - test("client error", async () => { - const getFid = (_custody: Hex) => Promise.reject(new Error("client error")); + test('client error', async () => { + const getFid = (_custody: Hex) => Promise.reject(new Error('client error')) const res = createSiweMessage({ ...siweParams, address: account.address, fid: 1234, - }); - const siweMessage = res; - const message = siweMessage.toMessage(); + }) + const siweMessage = res + const message = siweMessage.toMessage() const signature = await account.signMessage({ message, - }); + }) try { - await verifySiweMessageWithVerifier({ nonce, domain, message, signature, verifier: { getFid } }); - expect(true).toBe(false); + await verifySiweMessageWithVerifier({ + nonce, + domain, + message, + signature, + verifier: { getFid }, + }) + expect(true).toBe(false) } catch (e) { - expect(e).toBeInstanceOf(AuthClientError); - if (!(e instanceof AuthClientError)) throw new Error("unexpected"); + expect(e).toBeInstanceOf(AuthClientError) + if (!(e instanceof AuthClientError)) throw new Error('unexpected') - expect(e.errCode).toBe("unavailable"); - expect(e.message).toBe("client error"); + expect(e.errCode).toBe('unavailable') + expect(e.message).toBe('client error') } - }); + }) - test("mismatched nonce", async () => { + test('mismatched nonce', async () => { const res = createSiweMessage({ ...siweParams, address: account.address, fid: 1234, - }); - const siweMessage = res; - const message = siweMessage.toMessage(); + }) + const siweMessage = res + const message = siweMessage.toMessage() const signature = await account.signMessage({ message, - }); - const getFid = (_custody: Hex) => Promise.resolve(1234n); + }) + const getFid = (_custody: Hex) => Promise.resolve(1234n) try { await verifySiweMessageWithVerifier({ - nonce: "mismatched-nonce", + nonce: 'mismatched-nonce', domain, message, signature, verifier: { getFid }, - }); - expect(true).toBe(false); + }) + expect(true).toBe(false) } catch (e) { - expect(e).toBeInstanceOf(AuthClientError); - if (!(e instanceof AuthClientError)) throw new Error("unexpected"); + expect(e).toBeInstanceOf(AuthClientError) + if (!(e instanceof AuthClientError)) throw new Error('unexpected') - expect(e.errCode).toBe("unauthorized"); - expect(e.message).toBe("Invalid nonce"); + expect(e.errCode).toBe('unauthorized') + expect(e.message).toBe('Invalid nonce') } - }); + }) - test("mismatched domain", async () => { + test('mismatched domain', async () => { const res = createSiweMessage({ ...siweParams, address: account.address, fid: 1234, - }); - const siweMessage = res; - const message = siweMessage.toMessage(); + }) + const siweMessage = res + const message = siweMessage.toMessage() const signature = await account.signMessage({ message, - }); - const getFid = (_custody: Hex) => Promise.resolve(1234n); + }) + const getFid = (_custody: Hex) => Promise.resolve(1234n) try { await verifySiweMessageWithVerifier({ nonce, - domain: "mismatched-domain", + domain: 'mismatched-domain', message, signature, verifier: { getFid }, - }); - expect(true).toBe(false); + }) + expect(true).toBe(false) } catch (e) { - expect(e).toBeInstanceOf(AuthClientError); - if (!(e instanceof AuthClientError)) throw new Error("unexpected"); + expect(e).toBeInstanceOf(AuthClientError) + if (!(e instanceof AuthClientError)) throw new Error('unexpected') - expect(e.errCode).toBe("unauthorized"); - expect(e.message).toBe("Invalid domain"); + expect(e.errCode).toBe('unauthorized') + expect(e.message).toBe('Invalid domain') } - }); -}); + }) +}) diff --git a/packages/core/src/utils/verifySiweMessageWithVerifier.ts b/packages/core/src/utils/verifySiweMessageWithVerifier.ts index 08723a7..ea9adfb 100644 --- a/packages/core/src/utils/verifySiweMessageWithVerifier.ts +++ b/packages/core/src/utils/verifySiweMessageWithVerifier.ts @@ -1,25 +1,30 @@ -import { SiweMessage, type SiweResponse, SiweError } from "siwe"; -import { AuthClientError } from "../errors.js"; +import { SiweError, type SiweMessage, type SiweResponse } from 'siwe' +import { AuthClientError } from '../errors.js' -import { type FarcasterResourceParameters } from "./createSiweMessage.js"; -import type { Provider } from "ethers"; -import { getSiweMessage } from "./getSiweMessage.js"; -import { getResourcesFromSiweMessage } from "./getResourcesFromSiweMessage.js"; -import type { Address } from "../types/address.js"; +import type { Provider } from 'ethers' +import type { Address } from '../types/address.js' +import type { FarcasterResourceParameters } from './createSiweMessage.js' +import { getResourcesFromSiweMessage } from './getResourcesFromSiweMessage.js' +import { getSiweMessage } from './getSiweMessage.js' export type VerifySiweMessageWithVerifierParameters = { - nonce: string; - domain: string; - message: string | Partial; - signature: string; + nonce: string + domain: string + message: string | Partial + signature: string verifier: { - getFid: (custody: Address) => Promise; - provider?: Provider; - }; -}; -export type VerifySiweMessageWithVerifierReturnType = Omit & FarcasterResourceParameters; + getFid: (custody: Address) => Promise + provider?: Provider + } +} +export type VerifySiweMessageWithVerifierReturnType = Omit< + SiweResponse, + 'error' +> & + FarcasterResourceParameters -const voidVerifyFid = (_custody: Address) => Promise.reject(new Error("Not implemented: Must provide an fid verifier")); +const voidVerifyFid = (_custody: Address) => + Promise.reject(new Error('Not implemented: Must provide an fid verifier')) /** * Verify signature of a Sign In With Farcaster message. Returns an error if the @@ -36,72 +41,91 @@ export const verifySiweMessageWithVerifier = async ( verifier = { getFid: voidVerifyFid, }, - } = parameters; + } = parameters - const { getFid, provider } = verifier; - const validatedMessage = getSiweMessage({ message }); - verifyNonce(validatedMessage, nonce); - verifyDomain(validatedMessage, domain); + const { getFid, provider } = verifier + const validatedMessage = getSiweMessage({ message }) + verifyNonce(validatedMessage, nonce) + verifyDomain(validatedMessage, domain) - const siweResponse = await verifySignature(validatedMessage, signature, provider); - const siweResponseWithResources = mergeResources(siweResponse); + const siweResponse = await verifySignature( + validatedMessage, + signature, + provider, + ) + const siweResponseWithResources = mergeResources(siweResponse) if (!siweResponseWithResources.success) { - const errMessage = siweResponseWithResources.error?.type ?? "Failed to verify SIWE message"; - throw new AuthClientError("unauthorized", errMessage); + const errMessage = + siweResponseWithResources.error?.type ?? 'Failed to verify SIWE message' + throw new AuthClientError('unauthorized', errMessage) } - const siweResponseWithResourcesAndValidatedFid = await verifyFidOwner(siweResponseWithResources, getFid); + const siweResponseWithResourcesAndValidatedFid = await verifyFidOwner( + siweResponseWithResources, + getFid, + ) if (!siweResponseWithResourcesAndValidatedFid.success) { - const errMessage = siweResponseWithResourcesAndValidatedFid.error?.type ?? "Failed to validate fid owner"; - throw new AuthClientError("unauthorized", errMessage); + const errMessage = + siweResponseWithResourcesAndValidatedFid.error?.type ?? + 'Failed to validate fid owner' + throw new AuthClientError('unauthorized', errMessage) } - const { error, ...response } = siweResponseWithResourcesAndValidatedFid; - return response; -}; + const { error, ...response } = siweResponseWithResourcesAndValidatedFid + return response +} -const verifySignature = async (message: SiweMessage, signature: string, provider?: Provider): Promise => { +const verifySignature = async ( + message: SiweMessage, + signature: string, + provider?: Provider, +): Promise => { try { - return await message.verify({ signature }, { provider, suppressExceptions: true }); + return await message.verify( + { signature }, + { provider, suppressExceptions: true }, + ) } catch (e) { - throw new AuthClientError("unauthorized", e as Error); + throw new AuthClientError('unauthorized', e as Error) } -}; +} const verifyFidOwner = async ( response: SiweResponse & FarcasterResourceParameters, fidVerifier: (custody: Address) => Promise, ): Promise => { - const signer = response.data.address as Address; + const signer = response.data.address as Address try { - const fid = await fidVerifier(signer); + const fid = await fidVerifier(signer) if (fid !== BigInt(response.fid)) { - response.success = false; + response.success = false response.error = new SiweError( `Invalid resource: signer ${signer} does not own fid ${response.fid}.`, response.fid.toString(), fid.toString(), - ); + ) } - return response; + return response } catch (e) { - throw new AuthClientError("unavailable", e as Error); + throw new AuthClientError('unavailable', e as Error) } -}; +} -const mergeResources = (response: SiweResponse): SiweResponse & FarcasterResourceParameters => { - const resources = getResourcesFromSiweMessage({ message: response.data }); - return { ...response, ...resources }; -}; +const mergeResources = ( + response: SiweResponse, +): SiweResponse & FarcasterResourceParameters => { + const resources = getResourcesFromSiweMessage({ message: response.data }) + return { ...response, ...resources } +} export const verifyNonce = (message: SiweMessage, nonce: string): void => { if (message.nonce !== nonce) { - throw new AuthClientError("unauthorized", "Invalid nonce"); + throw new AuthClientError('unauthorized', 'Invalid nonce') } -}; +} export const verifyDomain = (message: SiweMessage, domain: string): void => { if (message.domain !== domain) { - throw new AuthClientError("unauthorized", "Invalid domain"); + throw new AuthClientError('unauthorized', 'Invalid domain') } -}; +} diff --git a/packages/react/.eslintrc.cjs b/packages/react/.eslintrc.cjs index 78174f6..d6c9537 100644 --- a/packages/react/.eslintrc.cjs +++ b/packages/react/.eslintrc.cjs @@ -1,11 +1,18 @@ module.exports = { root: true, env: { browser: true, es2020: true }, - extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:react-hooks/recommended"], - ignorePatterns: ["dist", ".eslintrc.cjs"], - parser: "@typescript-eslint/parser", - plugins: ["react-refresh"], + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:react-hooks/recommended', + ], + ignorePatterns: ['dist', '.eslintrc.cjs'], + parser: '@typescript-eslint/parser', + plugins: ['react-refresh'], rules: { - "react-refresh/only-export-components": ["warn", { allowConstantExport: true }], + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], }, -}; +} diff --git a/packages/react/src/actions/createChannel.ts b/packages/react/src/actions/createChannel.ts index 43f5fb2..cf1aaa9 100644 --- a/packages/react/src/actions/createChannel.ts +++ b/packages/react/src/actions/createChannel.ts @@ -1,25 +1,35 @@ -import { +import type { AuthClientError, - type CreateChannelParameters as client_CreateChannelParameters, - type CreateChannelReturnType as client_CreateChannelReturnType, -} from "@fc-auth/core"; -import { type Config } from "../types/config.js"; -import type { Omit } from "../types/utils.js"; + CreateChannelParameters as client_CreateChannelParameters, + CreateChannelReturnType as client_CreateChannelReturnType, +} from '@fc-auth/core' +import type { Config } from '../types/config.js' +import type { Omit } from '../types/utils.js' -export type CreateChannelParameters = Omit & { - nonce?: string | (() => Promise); -}; +export type CreateChannelParameters = Omit< + client_CreateChannelParameters, + 'nonce' +> & { + nonce?: string | (() => Promise) +} -export type CreateChannelReturnType = client_CreateChannelReturnType; -export type CreateChannelErrorType = AuthClientError; +export type CreateChannelReturnType = client_CreateChannelReturnType +export type CreateChannelErrorType = AuthClientError export async function createChannel( config: Config, parameters: CreateChannelParameters, ): Promise { - const { nonce, expirationTime, notBefore, requestId, siweUri = config.siweUri, domain = config.domain } = parameters; + const { + nonce, + expirationTime, + notBefore, + requestId, + siweUri = config.siweUri, + domain = config.domain, + } = parameters - const nonceVal = typeof nonce === "function" ? await nonce() : nonce; + const nonceVal = typeof nonce === 'function' ? await nonce() : nonce return await config.appClient.createChannel({ nonce: nonceVal, siweUri, @@ -27,5 +37,5 @@ export async function createChannel( notBefore, expirationTime, requestId, - }); + }) } diff --git a/packages/react/src/actions/pollChannelTillCompleted.ts b/packages/react/src/actions/pollChannelTillCompleted.ts index 9581adf..ba947e9 100644 --- a/packages/react/src/actions/pollChannelTillCompleted.ts +++ b/packages/react/src/actions/pollChannelTillCompleted.ts @@ -1,28 +1,34 @@ -import { +import type { AuthClientError, - type PollChannelTillCompletedParameters as client_PollChannelTilCompletedParameters, - type PollChannelTillCompletedReturnType as client_PollChannelTilCompletedReturnType, -} from "@fc-auth/core"; -import { type Config } from "../types/config.js"; + PollChannelTillCompletedParameters as client_PollChannelTilCompletedParameters, + PollChannelTillCompletedReturnType as client_PollChannelTilCompletedReturnType, +} from '@fc-auth/core' +import type { Config } from '../types/config.js' -export type PollChannelTillCompletedParameters = client_PollChannelTilCompletedParameters; +export type PollChannelTillCompletedParameters = + client_PollChannelTilCompletedParameters -export type PollChannelTillCompletedReturnType = client_PollChannelTilCompletedReturnType; -export type PollChannelTillCompletedErrorType = AuthClientError; +export type PollChannelTillCompletedReturnType = + client_PollChannelTilCompletedReturnType +export type PollChannelTillCompletedErrorType = AuthClientError const defaults = { timeout: 300_000, interval: 1_500, -}; +} export async function pollChannelTillCompleted( config: Config, parameters: PollChannelTillCompletedParameters, ): Promise { - const { channelToken, timeout = defaults.timeout, interval = defaults.interval } = parameters; + const { + channelToken, + timeout = defaults.timeout, + interval = defaults.interval, + } = parameters return await config.appClient.pollChannelTillCompleted({ channelToken, timeout, interval, - }); + }) } diff --git a/packages/react/src/actions/signIn.ts b/packages/react/src/actions/signIn.ts index 4f50a4c..6c2f20d 100644 --- a/packages/react/src/actions/signIn.ts +++ b/packages/react/src/actions/signIn.ts @@ -1,38 +1,49 @@ -import { +import type { AuthClientError, - type PollChannelTillCompletedReturnType, - type PollChannelTillCompletedParameters, -} from "@fc-auth/core"; -import { type Config } from "../types/config.js"; -import { type Omit } from "../types/utils.js"; + PollChannelTillCompletedParameters, + PollChannelTillCompletedReturnType, +} from '@fc-auth/core' +import type { Config } from '../types/config.js' +import type { Omit } from '../types/utils.js' -export type SignInErrorType = AuthClientError; +export type SignInErrorType = AuthClientError -export type SignInParameters = Omit & { - channelToken: string; -}; +export type SignInParameters = Omit< + PollChannelTillCompletedParameters, + 'channelToken' +> & { + channelToken: string +} -export type SignInReturnType = PollChannelTillCompletedReturnType & { isAuthenticated: boolean }; +export type SignInReturnType = PollChannelTillCompletedReturnType & { + isAuthenticated: boolean +} const defaults = { timeout: 300_000, interval: 1_500, -}; +} -export async function signIn(config: Config, parameters: SignInParameters): Promise { - if (!config.domain) throw new Error("domain is not defined"); - const pollChannelTillCompletedResponse = await config.appClient.pollChannelTillCompleted({ - channelToken: parameters?.channelToken, - timeout: parameters?.timeout ?? defaults.timeout, - interval: parameters?.interval ?? defaults.interval, - }); +export async function signIn( + config: Config, + parameters: SignInParameters, +): Promise { + if (!config.domain) throw new Error('domain is not defined') + const pollChannelTillCompletedResponse = + await config.appClient.pollChannelTillCompleted({ + channelToken: parameters?.channelToken, + timeout: parameters?.timeout ?? defaults.timeout, + interval: parameters?.interval ?? defaults.interval, + }) - const { success: isAuthenticated } = await config.appClient.verifySiweMessage({ - nonce: pollChannelTillCompletedResponse.nonce, - domain: config.domain, - message: pollChannelTillCompletedResponse.message, - signature: pollChannelTillCompletedResponse.signature, - }); + const { success: isAuthenticated } = await config.appClient.verifySiweMessage( + { + nonce: pollChannelTillCompletedResponse.nonce, + domain: config.domain, + message: pollChannelTillCompletedResponse.message, + signature: pollChannelTillCompletedResponse.signature, + }, + ) - return { isAuthenticated, ...pollChannelTillCompletedResponse }; + return { isAuthenticated, ...pollChannelTillCompletedResponse } } diff --git a/packages/react/src/actions/verifySiweMessage.ts b/packages/react/src/actions/verifySiweMessage.ts index 828a7cd..d9d8dc2 100644 --- a/packages/react/src/actions/verifySiweMessage.ts +++ b/packages/react/src/actions/verifySiweMessage.ts @@ -1,25 +1,25 @@ -import { +import type { AuthClientError, - type VerifySiweMessageParameters as client_VerifySiweMessageParameters, - type VerifySiweMessageReturnType as client_VerifySiweMessageReturnType, -} from "@fc-auth/core"; -import { type Config } from "../types/config.js"; + VerifySiweMessageParameters as client_VerifySiweMessageParameters, + VerifySiweMessageReturnType as client_VerifySiweMessageReturnType, +} from '@fc-auth/core' +import type { Config } from '../types/config.js' -export type VerifySiweMessageParameters = client_VerifySiweMessageParameters; +export type VerifySiweMessageParameters = client_VerifySiweMessageParameters -export type VerifySiweMessageReturnType = client_VerifySiweMessageReturnType; -export type VerifySiweMessageErrorType = AuthClientError; +export type VerifySiweMessageReturnType = client_VerifySiweMessageReturnType +export type VerifySiweMessageErrorType = AuthClientError export async function verifySiweMessage( config: Config, parameters: VerifySiweMessageParameters, ): Promise { - const { nonce, domain, message, signature } = parameters; + const { nonce, domain, message, signature } = parameters return await config.appClient.verifySiweMessage({ nonce, domain, message, signature, - }); + }) } diff --git a/packages/react/src/components/ActionButton/ActionButton.tsx b/packages/react/src/components/ActionButton/ActionButton.tsx index dee61d2..6fa70bf 100644 --- a/packages/react/src/components/ActionButton/ActionButton.tsx +++ b/packages/react/src/components/ActionButton/ActionButton.tsx @@ -1,21 +1,21 @@ -"use client"; +'use client' -import { FarcasterLogo } from "../FarcasterLogo.js"; -import { Button } from "../Button.js"; +import { Button } from '../Button.js' +import { FarcasterLogo } from '../FarcasterLogo.js' export function ActionButton({ label, disabled, onClick, }: { - label: string; - disabled?: boolean; - onClick: () => void; + label: string + disabled?: boolean + onClick: () => void }) { return ( - ); + ) } diff --git a/packages/react/src/components/ActionButton/index.ts b/packages/react/src/components/ActionButton/index.ts index 7f49e38..60f61e3 100644 --- a/packages/react/src/components/ActionButton/index.ts +++ b/packages/react/src/components/ActionButton/index.ts @@ -1 +1 @@ -export * from "./ActionButton.js"; +export * from './ActionButton.js' diff --git a/packages/react/src/components/AuthKitProvider/AuthKitProvider.tsx b/packages/react/src/components/AuthKitProvider/AuthKitProvider.tsx index 218a7ba..9f27cc7 100644 --- a/packages/react/src/components/AuthKitProvider/AuthKitProvider.tsx +++ b/packages/react/src/components/AuthKitProvider/AuthKitProvider.tsx @@ -1,20 +1,22 @@ -"use client"; +'use client' -import { createContext, type ReactNode } from "react"; -import { type Config } from "../../types/config.js"; +import { type ReactNode, createContext } from 'react' +import type { Config } from '../../types/config.js' export interface AuthKitConfigContextValues { - config: Config; + config: Config } -export const AuthKitContext = createContext(null); +export const AuthKitContext = createContext( + null, +) export function AuthKitProvider({ config, children, }: { - config: Config; - children: ReactNode; + config: Config + children: ReactNode }) { return ( {children} - ); + ) } diff --git a/packages/react/src/components/AuthKitProvider/config.ts b/packages/react/src/components/AuthKitProvider/config.ts index fef2bcc..dcd37f3 100644 --- a/packages/react/src/components/AuthKitProvider/config.ts +++ b/packages/react/src/components/AuthKitProvider/config.ts @@ -1,29 +1,29 @@ -import { createAppClient, viemConnector } from "@fc-auth/core"; -import { type Config } from "../../types/config.js"; +import { createAppClient, viemConnector } from '@fc-auth/core' +import type { Config } from '../../types/config.js' const domainDefaults = - typeof window !== "undefined" && window?.location + typeof window !== 'undefined' && window?.location ? { domain: window.location.host, siweUri: window.location.href, } - : {}; + : {} const configDefaults = { - relay: "https://relay.farcaster.xyz", - version: "v1", + relay: 'https://relay.farcaster.xyz', + version: 'v1', ...domainDefaults, -}; +} -export function createConfig(config: Omit): Config { +export function createConfig(config: Omit): Config { const authKitConfig = { ...configDefaults, ...config, - }; + } - const { relay, rpcUrl, version, provider } = authKitConfig; + const { relay, rpcUrl, version, provider } = authKitConfig - const ethereum = viemConnector(rpcUrl ? { rpcUrl } : undefined); + const ethereum = viemConnector(rpcUrl ? { rpcUrl } : undefined) const appClient = createAppClient( { relay, @@ -31,6 +31,6 @@ export function createConfig(config: Omit): Config { version, }, provider, - ); - return { ...authKitConfig, appClient }; + ) + return { ...authKitConfig, appClient } } diff --git a/packages/react/src/components/AuthKitProvider/index.ts b/packages/react/src/components/AuthKitProvider/index.ts index e9f2b90..1e8a209 100644 --- a/packages/react/src/components/AuthKitProvider/index.ts +++ b/packages/react/src/components/AuthKitProvider/index.ts @@ -1,2 +1,2 @@ -export { AuthKitProvider } from "./AuthKitProvider.js"; -export { createConfig } from "./config.js"; +export { AuthKitProvider } from './AuthKitProvider.js' +export { createConfig } from './config.js' diff --git a/packages/react/src/components/Button.tsx b/packages/react/src/components/Button.tsx index 5ed4565..7206ec2 100644 --- a/packages/react/src/components/Button.tsx +++ b/packages/react/src/components/Button.tsx @@ -1,16 +1,26 @@ -import { type ButtonHTMLAttributes } from "react"; -import { primaryButton, secondaryButton, tertiaryButton, resetButton } from "./styles.css.js"; +import type { ButtonHTMLAttributes } from 'react' +import { + primaryButton, + resetButton, + secondaryButton, + tertiaryButton, +} from './styles.css.js' export type Props = ButtonHTMLAttributes & { - kind?: "primary" | "secondary" | "tertiary" | "reset"; -}; + kind?: 'primary' | 'secondary' | 'tertiary' | 'reset' +} -export function Button({ kind = "primary", children, className, ...rest }: Props) { +export function Button({ + kind = 'primary', + children, + className, + ...rest +}: Props) { return ( - ); + ) } const kindClass = { @@ -18,4 +28,4 @@ const kindClass = { secondary: secondaryButton, tertiary: tertiaryButton, reset: resetButton, -}; +} diff --git a/packages/react/src/components/Demo/Demo.tsx b/packages/react/src/components/Demo/Demo.tsx index f78e7ed..afdc5c0 100644 --- a/packages/react/src/components/Demo/Demo.tsx +++ b/packages/react/src/components/Demo/Demo.tsx @@ -1,38 +1,38 @@ -"use client"; +'use client' -import useProfile from "../../hooks/useProfile.js"; -import { SignInButton } from "../SignInButton/index.js"; -import { AuthKitProvider, createConfig } from "../AuthKitProvider/index.js"; +import useProfile from '../../hooks/useProfile.js' +import { AuthKitProvider, createConfig } from '../AuthKitProvider/index.js' +import { SignInButton } from '../SignInButton/index.js' export function Demo() { const config = createConfig({ - rpcUrl: "https://mainnet.optimism.io", - siweUri: "https://example.com/login", - domain: "example.com", - }); + rpcUrl: 'https://mainnet.optimism.io', + siweUri: 'https://example.com/login', + domain: 'example.com', + }) return ( -
+
console.error("error callback:", error)} - onSignIn={(data) => console.log("success callback:", data)} - onSignOut={() => console.log("sign out callback")} + onSignInError={(error) => console.error('error callback:', error)} + onSignIn={(data) => console.log('success callback:', data)} + onSignOut={() => console.log('sign out callback')} />
- ); + ) } function UserProfile() { - const profile = useProfile(); + const profile = useProfile() return ( -
+
{profile?.isAuthenticated && (
{profile.fid && ( @@ -58,5 +58,5 @@ function UserProfile() {
)}
- ); + ) } diff --git a/packages/react/src/components/Demo/index.ts b/packages/react/src/components/Demo/index.ts index 8ee4c6a..00970cc 100644 --- a/packages/react/src/components/Demo/index.ts +++ b/packages/react/src/components/Demo/index.ts @@ -1 +1 @@ -export * from "./Demo.js"; +export * from './Demo.js' diff --git a/packages/react/src/components/Dialog/Dialog.css.ts b/packages/react/src/components/Dialog/Dialog.css.ts index 8cf2808..9759589 100644 --- a/packages/react/src/components/Dialog/Dialog.css.ts +++ b/packages/react/src/components/Dialog/Dialog.css.ts @@ -1,37 +1,37 @@ -import { keyframes, style } from "@vanilla-extract/css"; +import { keyframes, style } from '@vanilla-extract/css' const slideUp = keyframes({ - "0%": { transform: "translateY(100%)" }, - "100%": { transform: "translateY(0)" }, -}); + '0%': { transform: 'translateY(100%)' }, + '100%': { transform: 'translateY(0)' }, +}) const fadeIn = keyframes({ - "0%": { opacity: 0 }, - "100%": { opacity: 1 }, -}); + '0%': { opacity: 0 }, + '100%': { opacity: 1 }, +}) -const bleed = 200; +const bleed = 200 export const overlay = style({ - backdropFilter: "modalOverlay", - background: "rgba(0, 0, 0, 0.3)", - display: "flex", - justifyContent: "center", - position: "fixed", + backdropFilter: 'modalOverlay', + background: 'rgba(0, 0, 0, 0.3)', + display: 'flex', + justifyContent: 'center', + position: 'fixed', animation: `${fadeIn} 150ms ease`, bottom: -bleed, left: -bleed, padding: bleed, right: -bleed, top: -bleed, - transform: "translateZ(0)", // This is required for content to render under the URL bar on iOS + transform: 'translateZ(0)', // This is required for content to render under the URL bar on iOS zIndex: 999999999, -}); +}) export const content = style({ - display: "flex", - flexDirection: "column", - position: "relative", + display: 'flex', + flexDirection: 'column', + position: 'relative', animation: `${slideUp} 350ms cubic-bezier(.15,1.15,0.6,1.00), ${fadeIn} 150ms ease`, - maxWidth: "100vw", -}); + maxWidth: '100vw', +}) diff --git a/packages/react/src/components/Dialog/Dialog.tsx b/packages/react/src/components/Dialog/Dialog.tsx index 282df1b..965b702 100644 --- a/packages/react/src/components/Dialog/Dialog.tsx +++ b/packages/react/src/components/Dialog/Dialog.tsx @@ -1,37 +1,47 @@ -"use client"; +'use client' -import { type MouseEventHandler, type ReactNode, useCallback, useEffect, useState } from "react"; -import { createPortal } from "react-dom"; -import { RemoveScroll } from "react-remove-scroll"; -import * as styles from "./Dialog.css.js"; -import { FocusTrap } from "./FocusTrap.js"; -import { isMobile } from "../../utils.js"; +import { + type MouseEventHandler, + type ReactNode, + useCallback, + useEffect, + useState, +} from 'react' +import { createPortal } from 'react-dom' +import { RemoveScroll } from 'react-remove-scroll' +import { isMobile } from '../../utils.js' +import * as styles from './Dialog.css.js' +import { FocusTrap } from './FocusTrap.js' -const stopPropagation: MouseEventHandler = (event) => event.stopPropagation(); +const stopPropagation: MouseEventHandler = (event) => + event.stopPropagation() interface DialogProps { - open: boolean; - onClose: () => void; - titleId: string; - onMountAutoFocus?: (event: Event) => void; - children: ReactNode; + open: boolean + onClose: () => void + titleId: string + onMountAutoFocus?: (event: Event) => void + children: ReactNode } export function Dialog({ children, onClose, open, titleId }: DialogProps) { useEffect(() => { - const handleEscape = (event: KeyboardEvent) => open && event.key === "Escape" && onClose(); + const handleEscape = (event: KeyboardEvent) => + open && event.key === 'Escape' && onClose() - document.addEventListener("keydown", handleEscape); + document.addEventListener('keydown', handleEscape) - return () => document.removeEventListener("keydown", handleEscape); - }, [open, onClose]); + return () => document.removeEventListener('keydown', handleEscape) + }, [open, onClose]) - const [bodyScrollable, setBodyScrollable] = useState(true); + const [bodyScrollable, setBodyScrollable] = useState(true) useEffect(() => { - setBodyScrollable(getComputedStyle(window.document.body).overflow !== "hidden"); - }, []); + setBodyScrollable( + getComputedStyle(window.document.body).overflow !== 'hidden', + ) + }, []) - const handleBackdropClick = useCallback(() => onClose(), [onClose]); + const handleBackdropClick = useCallback(() => onClose(), [onClose]) return ( <> @@ -40,8 +50,8 @@ export function Dialog({ children, onClose, open, titleId }: DialogProps) {
- + {children}
@@ -58,5 +72,5 @@ export function Dialog({ children, onClose, open, titleId }: DialogProps) { ) : null} - ); + ) } diff --git a/packages/react/src/components/Dialog/FocusTrap.tsx b/packages/react/src/components/Dialog/FocusTrap.tsx index fb139fc..092f13f 100644 --- a/packages/react/src/components/Dialog/FocusTrap.tsx +++ b/packages/react/src/components/Dialog/FocusTrap.tsx @@ -1,52 +1,68 @@ -"use client"; +'use client' -import { useCallback, useEffect, useRef } from "react"; +import { useCallback, useEffect, useRef } from 'react' -const moveFocusWithin = (element: HTMLElement, position: "start" | "end") => { - const focusableElements = element.querySelectorAll("button:not(:disabled), a[href]") as NodeListOf< - HTMLButtonElement | HTMLAnchorElement - >; +const moveFocusWithin = (element: HTMLElement, position: 'start' | 'end') => { + const focusableElements = element.querySelectorAll( + 'button:not(:disabled), a[href]', + ) as NodeListOf - if (focusableElements.length === 0) return; + if (focusableElements.length === 0) return - focusableElements[position === "end" ? focusableElements.length - 1 : 0]?.focus(); -}; + focusableElements[ + position === 'end' ? focusableElements.length - 1 : 0 + ]?.focus() +} -export function FocusTrap(props: JSX.IntrinsicElements["div"]) { - const contentRef = useRef(null); +export function FocusTrap(props: JSX.IntrinsicElements['div']) { + const contentRef = useRef(null) useEffect(() => { - const previouslyActiveElement = document.activeElement; + const previouslyActiveElement = document.activeElement return () => { - (previouslyActiveElement as HTMLElement).focus?.(); - }; - }, []); + ;(previouslyActiveElement as HTMLElement).focus?.() + } + }, []) useEffect(() => { if (contentRef.current) { - const elementToFocus = contentRef.current.querySelector("[data-auto-focus]"); + const elementToFocus = + contentRef.current.querySelector('[data-auto-focus]') if (elementToFocus) { - (elementToFocus as HTMLElement).focus(); + ;(elementToFocus as HTMLElement).focus() } else { - contentRef.current.focus(); + contentRef.current.focus() } } - }, []); + }, []) return ( <>
contentRef.current && moveFocusWithin(contentRef.current, "end"), [])} + onFocus={useCallback( + () => + contentRef.current && moveFocusWithin(contentRef.current, 'end'), + [], + )} // biome-ignore lint/a11y/noNoninteractiveTabindex: incorrect tabIndex={0} /> -
contentRef.current && moveFocusWithin(contentRef.current, "start"), [])} + ref={contentRef} + style={{ outline: 'none' }} + tabIndex={-1} + {...props} + /> +
+ contentRef.current && moveFocusWithin(contentRef.current, 'start'), + [], + )} // biome-ignore lint/a11y/noNoninteractiveTabindex: incorrect tabIndex={0} /> - ); + ) } diff --git a/packages/react/src/components/Dialog/index.ts b/packages/react/src/components/Dialog/index.ts index 659fd15..ad3a252 100644 --- a/packages/react/src/components/Dialog/index.ts +++ b/packages/react/src/components/Dialog/index.ts @@ -1 +1 @@ -export * from "./Dialog.js"; +export * from './Dialog.js' diff --git a/packages/react/src/components/FarcasterLogo.tsx b/packages/react/src/components/FarcasterLogo.tsx index 4d5ad2f..6937860 100644 --- a/packages/react/src/components/FarcasterLogo.tsx +++ b/packages/react/src/components/FarcasterLogo.tsx @@ -2,13 +2,18 @@ export function FarcasterLogo({ height, fill, }: { - height: number; - fill: "purple" | "white"; + height: number + fill: 'purple' | 'white' }) { - const width = height * 1.1; + const width = height * 1.1 return ( - + Farcaster logo @@ -20,10 +25,10 @@ export function FarcasterLogo({ - ); + ) } const fillColor = { - purple: "#7C65C1", - white: "#FFFFFF", -}; + purple: '#7C65C1', + white: '#FFFFFF', +} diff --git a/packages/react/src/components/ProfileButton/ProfileButton.css.ts b/packages/react/src/components/ProfileButton/ProfileButton.css.ts index 1b69d8d..643fd62 100644 --- a/packages/react/src/components/ProfileButton/ProfileButton.css.ts +++ b/packages/react/src/components/ProfileButton/ProfileButton.css.ts @@ -1,24 +1,24 @@ -import { style } from "@vanilla-extract/css"; +import { style } from '@vanilla-extract/css' export const signOutButtonContainer = style({ - marginTop: "12px", - fontWeight: "400", - boxShadow: "0px 6px 12px 0 rgba(0,0,0,0.12)", -}); + marginTop: '12px', + fontWeight: '400', + boxShadow: '0px 6px 12px 0 rgba(0,0,0,0.12)', +}) export const profileButtonContainer = style({ - display: "flex", - alignItems: "flex-start", -}); + display: 'flex', + alignItems: 'flex-start', +}) export const profileImage = style({ - objectFit: "cover", + objectFit: 'cover', width: 28, height: 28, borderRadius: 28, -}); +}) export const profileName = style({ marginLeft: 9, marginRight: 12, -}); +}) diff --git a/packages/react/src/components/ProfileButton/ProfileButton.tsx b/packages/react/src/components/ProfileButton/ProfileButton.tsx index 0067c60..bb0c081 100644 --- a/packages/react/src/components/ProfileButton/ProfileButton.tsx +++ b/packages/react/src/components/ProfileButton/ProfileButton.tsx @@ -1,15 +1,19 @@ -"use client"; +'use client' -import { useRef, useState } from "react"; -import { secondaryButton } from "../styles.css.js"; -import useDetectClickOutside from "../../hooks/useDetectClickOutside.js"; -import { profileButtonContainer, profileImage, profileName } from "./ProfileButton.css.js"; -import { SignOutButton } from "../SignOutButton/index.js"; +import { useRef, useState } from 'react' +import useDetectClickOutside from '../../hooks/useDetectClickOutside.js' +import { SignOutButton } from '../SignOutButton/index.js' +import { secondaryButton } from '../styles.css.js' +import { + profileButtonContainer, + profileImage, + profileName, +} from './ProfileButton.css.js' interface UserDataProps { - fid?: number; - pfpUrl?: string; - username?: string; + fid?: number + pfpUrl?: string + username?: string } export function ProfileButton({ @@ -17,33 +21,46 @@ export function ProfileButton({ signOut, hideSignOut, }: { - userData?: UserDataProps; - signOut?: () => void; - hideSignOut: boolean; + userData?: UserDataProps + signOut?: () => void + hideSignOut: boolean }) { - const [showSignOutButton, setShowSignOutButton] = useState(false); - const ref = useRef(null); - useDetectClickOutside(ref, () => setShowSignOutButton(false)); + const [showSignOutButton, setShowSignOutButton] = useState(false) + const ref = useRef(null) + useDetectClickOutside(ref, () => setShowSignOutButton(false)) - const name = userData?.username ?? `!${userData?.fid}`; - const pfpUrl = userData?.pfpUrl ?? "https://warpcast.com/avatar.png"; + const name = userData?.username ?? `!${userData?.fid}` + const pfpUrl = userData?.pfpUrl ?? 'https://warpcast.com/avatar.png' - const showSignOut = showSignOutButton && !hideSignOut; + const showSignOut = showSignOutButton && !hideSignOut return ( -
+
-
- ); + ) } diff --git a/packages/react/src/components/ProfileButton/index.ts b/packages/react/src/components/ProfileButton/index.ts index 007d1a3..926ae5c 100644 --- a/packages/react/src/components/ProfileButton/index.ts +++ b/packages/react/src/components/ProfileButton/index.ts @@ -1 +1 @@ -export * from "./ProfileButton.js"; +export * from './ProfileButton.js' diff --git a/packages/react/src/components/QRCode.tsx b/packages/react/src/components/QRCode.tsx index 9353f5c..1d4e71a 100644 --- a/packages/react/src/components/QRCode.tsx +++ b/packages/react/src/components/QRCode.tsx @@ -1,49 +1,64 @@ -"use client"; +'use client' -import QRCodeUtil from "qrcode"; -import { type ReactElement, useMemo } from "react"; -import { FarcasterLogo } from "./FarcasterLogo.js"; -import { qrCodeContainer, qrCodeWrapper, qrCode } from "./styles.css.js"; +import QRCodeUtil from 'qrcode' +import { type ReactElement, useMemo } from 'react' +import { FarcasterLogo } from './FarcasterLogo.js' +import { qrCode, qrCodeContainer, qrCodeWrapper } from './styles.css.js' -const generateMatrix = (value: string, errorCorrectionLevel: QRCodeUtil.QRCodeErrorCorrectionLevel) => { - const arr = Array.prototype.slice.call(QRCodeUtil.create(value, { errorCorrectionLevel }).modules.data, 0); - const sqrt = Math.sqrt(arr.length); +const generateMatrix = ( + value: string, + errorCorrectionLevel: QRCodeUtil.QRCodeErrorCorrectionLevel, +) => { + const arr = Array.prototype.slice.call( + QRCodeUtil.create(value, { errorCorrectionLevel }).modules.data, + 0, + ) + const sqrt = Math.sqrt(arr.length) return arr.reduce( - (rows, key, index) => (index % sqrt === 0 ? rows.push([key]) : rows[rows.length - 1].push(key)) && rows, + (rows, key, index) => + (index % sqrt === 0 + ? rows.push([key]) + : rows[rows.length - 1].push(key)) && rows, [], - ); -}; + ) +} type Props = { - ecl?: QRCodeUtil.QRCodeErrorCorrectionLevel; - logoUrl?: string; - logoMargin?: number; - logoSize?: number; - size?: number; - uri: string; -}; + ecl?: QRCodeUtil.QRCodeErrorCorrectionLevel + logoUrl?: string + logoMargin?: number + logoSize?: number + size?: number + uri: string +} -export function QRCode({ ecl = "H", logoMargin = 10, logoSize = 50, size: sizeProp = 200, uri }: Props) { - const padding = "20"; - const size = sizeProp - parseInt(padding, 10) * 2; +export function QRCode({ + ecl = 'H', + logoMargin = 10, + logoSize = 50, + size: sizeProp = 200, + uri, +}: Props) { + const padding = '20' + const size = sizeProp - Number.parseInt(padding, 10) * 2 const dots = useMemo(() => { - const dots: ReactElement[] = []; - const matrix = generateMatrix(uri, ecl); - const cellSize = size / matrix.length; + const dots: ReactElement[] = [] + const matrix = generateMatrix(uri, ecl) + const cellSize = size / matrix.length const qrList = [ { x: 0, y: 0 }, { x: 1, y: 0 }, { x: 0, y: 1 }, - ]; + ] qrList.forEach(({ x, y }) => { - const x1 = (matrix.length - 7) * cellSize * x; - const y1 = (matrix.length - 7) * cellSize * y; + const x1 = (matrix.length - 7) * cellSize * x + const y1 = (matrix.length - 7) * cellSize * y for (let i = 0; i < 3; i++) { dots.push( , - ); + ) } - }); + }) - const clearArenaSize = Math.floor((logoSize + logoMargin * 2) / cellSize); - const matrixMiddleStart = matrix.length / 2 - clearArenaSize / 2; - const matrixMiddleEnd = matrix.length / 2 + clearArenaSize / 2 - 1; + const clearArenaSize = Math.floor((logoSize + logoMargin * 2) / cellSize) + const matrixMiddleStart = matrix.length / 2 - clearArenaSize / 2 + const matrixMiddleEnd = matrix.length / 2 + clearArenaSize / 2 - 1 matrix.forEach((row: QRCodeUtil.QRCode[], i: number) => { row.forEach((_, j) => { if (matrix[i][j]) { - if (!((i < 7 && j < 7) || (i > matrix.length - 8 && j < 7) || (i < 7 && j > matrix.length - 8))) { - if (!(i > matrixMiddleStart && i < matrixMiddleEnd && j > matrixMiddleStart && j < matrixMiddleEnd)) { + if ( + !( + (i < 7 && j < 7) || + (i > matrix.length - 8 && j < 7) || + (i < 7 && j > matrix.length - 8) + ) + ) { + if ( + !( + i > matrixMiddleStart && + i < matrixMiddleEnd && + j > matrixMiddleStart && + j < matrixMiddleEnd + ) + ) { dots.push( , - ); + ) } } } - }); - }); + }) + }) - return dots; - }, [ecl, logoSize, logoMargin, size, uri]); + return dots + }, [ecl, logoSize, logoMargin, size, uri]) - const logoPosition = size / 2 - logoSize / 2; - const logoWrapperSize = logoSize + logoMargin * 2; + const logoPosition = size / 2 - logoSize / 2 + const logoWrapperSize = logoSize + logoMargin * 2 return (
@@ -98,7 +126,7 @@ export function QRCode({ ecl = "H", logoMargin = 10, logoSize = 50, size: sizePr >
- + QR Code @@ -113,5 +141,5 @@ export function QRCode({ ecl = "H", logoMargin = 10, logoSize = 50, size: sizePr
- ); + ) } diff --git a/packages/react/src/components/QRCodeDialog/QRCodeDialog.css.ts b/packages/react/src/components/QRCodeDialog/QRCodeDialog.css.ts index bba4fee..780f5c5 100644 --- a/packages/react/src/components/QRCodeDialog/QRCodeDialog.css.ts +++ b/packages/react/src/components/QRCodeDialog/QRCodeDialog.css.ts @@ -1,29 +1,29 @@ -import { style } from "@vanilla-extract/css"; +import { style } from '@vanilla-extract/css' export const body = style({ - backgroundColor: "white", - fontFamily: "sans-serif", + backgroundColor: 'white', + fontFamily: 'sans-serif', borderRadius: 12, maxWidth: 360, - position: "relative", + position: 'relative', paddingTop: 32, paddingLeft: 32, paddingRight: 32, paddingBottom: 20, -}); +}) export const siwfHeading = style({ fontSize: 22, fontWeight: 600, marginBottom: 6, -}); +}) export const instructions = style({ fontSize: 15.5, - color: "rgba(0, 0, 0, 0.5)", -}); + color: 'rgba(0, 0, 0, 0.5)', +}) export const qrCodeImage = style({ maxWidth: 480, - width: "100%", -}); + width: '100%', +}) diff --git a/packages/react/src/components/QRCodeDialog/QRCodeDialog.tsx b/packages/react/src/components/QRCodeDialog/QRCodeDialog.tsx index 96e8b30..faa2133 100644 --- a/packages/react/src/components/QRCodeDialog/QRCodeDialog.tsx +++ b/packages/react/src/components/QRCodeDialog/QRCodeDialog.tsx @@ -1,32 +1,45 @@ -"use client"; +'use client' -import { AuthClientError } from "@fc-auth/core"; -import { Dialog } from "../Dialog/index.js"; -import { body, siwfHeading, instructions } from "./QRCodeDialog.css.js"; -import { Button } from "../Button.js"; -import { QRCode } from "../QRCode.js"; +import type { AuthClientError } from '@fc-auth/core' +import { Button } from '../Button.js' +import { Dialog } from '../Dialog/index.js' +import { QRCode } from '../QRCode.js' +import { body, instructions, siwfHeading } from './QRCodeDialog.css.js' export function QRCodeDialog( props: { - open: boolean; - onClose: () => void; + open: boolean + onClose: () => void } & ( | { - variant: "success"; - url: string; + variant: 'success' + url: string } | { - variant: "error"; - error: AuthClientError; + variant: 'error' + error: AuthClientError } ), ) { return ( - +
-
- ); + ) } diff --git a/packages/react/src/components/QRCodeDialog/index.tsx b/packages/react/src/components/QRCodeDialog/index.tsx index 2948c28..919fe7e 100644 --- a/packages/react/src/components/QRCodeDialog/index.tsx +++ b/packages/react/src/components/QRCodeDialog/index.tsx @@ -1 +1 @@ -export { QRCodeDialog } from "./QRCodeDialog.js"; +export { QRCodeDialog } from './QRCodeDialog.js' diff --git a/packages/react/src/components/SignInButton/SignInButton.css.ts b/packages/react/src/components/SignInButton/SignInButton.css.ts index 05d3d66..ee2e15f 100644 --- a/packages/react/src/components/SignInButton/SignInButton.css.ts +++ b/packages/react/src/components/SignInButton/SignInButton.css.ts @@ -1,13 +1,13 @@ -import { style } from "@vanilla-extract/css"; +import { style } from '@vanilla-extract/css' export const debugPanel = style({ zIndex: 10, - position: "fixed", - backgroundColor: "white", + position: 'fixed', + backgroundColor: 'white', padding: 24, left: 9, bottom: 9, - boxShadow: "0 0 6px rgba(0, 0, 0, 0.3)", + boxShadow: '0 0 6px rgba(0, 0, 0, 0.3)', width: 600, - overflow: "scroll", -}); + overflow: 'scroll', +}) diff --git a/packages/react/src/components/SignInButton/SignInButton.tsx b/packages/react/src/components/SignInButton/SignInButton.tsx index d9a6784..4bc19e1 100644 --- a/packages/react/src/components/SignInButton/SignInButton.tsx +++ b/packages/react/src/components/SignInButton/SignInButton.tsx @@ -1,98 +1,165 @@ -"use client"; +'use client' -import { useCallback, useEffect, useState } from "react"; -import { AuthClientError, type PollChannelTillCompletedReturnType } from "@fc-auth/core"; -import useSignIn from "../../hooks/useSignIn.js"; -import { ActionButton } from "../ActionButton/index.js"; -import { ProfileButton } from "../ProfileButton/index.js"; -import { QRCodeDialog } from "../QRCodeDialog/index.js"; -import { isMobile, type MaybePromise } from "../../utils.js"; -import { type CreateChannelParameters } from "../../actions/createChannel.js"; -import { type SignInParameters } from "../../actions/signIn.js"; -import { type Omit } from "../../types/utils.js"; -import { useCreateChannel } from "../../hooks/useCreateChannel.js"; +import { + AuthClientError, + type PollChannelTillCompletedReturnType, +} from '@fc-auth/core' +import { useCallback, useEffect, useMemo, useState } from 'react' +import type { CreateChannelParameters } from '../../actions/createChannel.js' +import type { SignInParameters } from '../../actions/signIn.js' +import { useCreateChannel } from '../../hooks/useCreateChannel.js' +import useSignIn from '../../hooks/useSignIn.js' +import type { Omit } from '../../types/utils.js' +import { type MaybePromise, isMobile } from '../../utils.js' +import { ActionButton } from '../ActionButton/index.js' +import { ProfileButton } from '../ProfileButton/index.js' +import { QRCodeDialog } from '../QRCodeDialog/index.js' -type SignInButtonProps = Omit, "siweUri" | "domain"> & - Omit & { - onSignIn?: (signInData: PollChannelTillCompletedReturnType & { isAuthenticated: boolean }) => MaybePromise; - onSignOut?: () => MaybePromise; - onSignInError?: (error: unknown) => MaybePromise; - hideSignOut?: boolean; - }; +type SignInButtonProps = Omit< + NonNullable, + 'siweUri' | 'domain' +> & + Omit & { + onSignIn?: ( + signInData: PollChannelTillCompletedReturnType & { + isAuthenticated: boolean + }, + ) => MaybePromise + onSignOut?: () => MaybePromise + onSignInError?: (error: unknown) => MaybePromise + hideSignOut?: boolean + } -export function SignInButton({ hideSignOut, onSignOut, onSignInError, onSignIn, ...signInArgs }: SignInButtonProps) { - const { status: signInStatus, data: signInData, error: signInError, signIn, signOut, reset } = useSignIn(); +export function SignInButton({ + hideSignOut, + onSignOut, + onSignInError, + onSignIn, + nonce, + notBefore, + expirationTime, + requestId, + redirectUrl, + timeout, + interval, +}: SignInButtonProps) { + const signInArgs = useMemo( + () => ({ + nonce, + notBefore, + expirationTime, + requestId, + redirectUrl, + timeout, + interval, + }), + [ + nonce, + notBefore, + expirationTime, + requestId, + redirectUrl, + timeout, + interval, + ], + ) + const { + status: signInStatus, + data: signInData, + error: signInError, + signIn, + signOut, + reset, + } = useSignIn() const { data: createChannelData, status: createChannelDataChannel, error: createChannelError, refetch: recreateChannel, - } = useCreateChannel(signInArgs); + } = useCreateChannel(signInArgs) useEffect(() => { - if (signInStatus === "success") onSignIn?.(signInData); - if (signInStatus === "error") { + if (signInStatus === 'success') onSignIn?.(signInData) + if (signInStatus === 'error') { // if it's a polling error due to the timeout, we recreate the channel if ( signInError instanceof AuthClientError && - signInError.errCode === "unavailable" && - signInError.message.startsWith("Polling timed out after") + signInError.errCode === 'unavailable' && + signInError.message.startsWith('Polling timed out after') ) { - (async () => { - const { data: recreateChannelData } = await recreateChannel(); + ;(async () => { + const { data: recreateChannelData } = await recreateChannel() if (recreateChannelData?.channelToken) { - reset(); + reset() signIn({ channelToken: recreateChannelData.channelToken, timeout: signInArgs.timeout, interval: signInArgs.interval, - }); + }) } - })(); - return; + })() + return } - onSignInError?.(signInError); + onSignInError?.(signInError) } - }, [signInStatus, onSignIn, signInData, signInError, onSignInError, recreateChannel, reset, signIn, signInArgs]); + }, [ + signInStatus, + onSignIn, + signInData, + signInError, + onSignInError, + recreateChannel, + reset, + signIn, + signInArgs, + ]) const handleSignOut = useCallback(() => { - setShowDialog(false); - signOut(); - recreateChannel(); - onSignOut?.(); - }, [signOut, recreateChannel, onSignOut]); + setShowDialog(false) + signOut() + recreateChannel() + onSignOut?.() + }, [signOut, recreateChannel, onSignOut]) - const [showDialog, setShowDialog] = useState(false); + const [showDialog, setShowDialog] = useState(false) const onClick = useCallback(async () => { - if (signInStatus === "error") { - signOut(); + if (signInStatus === 'error') { + signOut() } - setShowDialog(true); + setShowDialog(true) - if (!createChannelData) throw new Error("Missing `createChannelData`"); - signIn({ ...signInArgs, channelToken: createChannelData.channelToken }); + if (!createChannelData) throw new Error('Missing `createChannelData`') + signIn({ ...signInArgs, channelToken: createChannelData.channelToken }) if (isMobile()) { - window.location.href = createChannelData.url; + window.location.href = createChannelData.url } - }, [signInStatus, signIn, createChannelData, signInArgs, signOut]); + }, [signInStatus, signIn, createChannelData, signInArgs, signOut]) return (
- {signInStatus === "success" && signInData.isAuthenticated ? ( - + {signInStatus === 'success' && signInData.isAuthenticated ? ( + ) : ( <> - - {createChannelDataChannel === "success" ? ( + + {createChannelDataChannel === 'success' ? ( setShowDialog(false)} url={createChannelData.url} /> - ) : createChannelDataChannel === "error" ? ( + ) : createChannelDataChannel === 'error' ? ( )}
- ); + ) } diff --git a/packages/react/src/components/SignInButton/index.ts b/packages/react/src/components/SignInButton/index.ts index 22420dd..2bb5f85 100644 --- a/packages/react/src/components/SignInButton/index.ts +++ b/packages/react/src/components/SignInButton/index.ts @@ -1 +1 @@ -export * from "./SignInButton.js"; +export * from './SignInButton.js' diff --git a/packages/react/src/components/SignOutButton/SignOutButton.css.ts b/packages/react/src/components/SignOutButton/SignOutButton.css.ts index 6fa7991..44a0d7c 100644 --- a/packages/react/src/components/SignOutButton/SignOutButton.css.ts +++ b/packages/react/src/components/SignOutButton/SignOutButton.css.ts @@ -1,10 +1,10 @@ -import { style } from "@vanilla-extract/css"; +import { style } from '@vanilla-extract/css' export const signOutButtonContainer = style({ - marginTop: "12px", - fontWeight: "400", -}); + marginTop: '12px', + fontWeight: '400', +}) export const signOutIcon = style({ marginRight: 9, -}); +}) diff --git a/packages/react/src/components/SignOutButton/SignOutButton.tsx b/packages/react/src/components/SignOutButton/SignOutButton.tsx index 11412f3..401d107 100644 --- a/packages/react/src/components/SignOutButton/SignOutButton.tsx +++ b/packages/react/src/components/SignOutButton/SignOutButton.tsx @@ -1,7 +1,7 @@ -"use client"; +'use client' -import { secondaryButton } from "../styles.css.js"; -import { signOutButtonContainer, signOutIcon } from "./SignOutButton.css.js"; +import { secondaryButton } from '../styles.css.js' +import { signOutButtonContainer, signOutIcon } from './SignOutButton.css.js' export function SignOutButton({ signOut }: { signOut?: () => void }) { return ( @@ -10,7 +10,7 @@ export function SignOutButton({ signOut }: { signOut?: () => void }) { type="button" className={secondaryButton} style={{ - boxShadow: "0px 6px 12px 0 rgba(0,0,0,0.12)", + boxShadow: '0px 6px 12px 0 rgba(0,0,0,0.12)', }} onClick={signOut} > @@ -34,7 +34,7 @@ export function SignOutButton({ signOut }: { signOut?: () => void }) { Sign out
- ); + ) } -export default SignOutButton; +export default SignOutButton diff --git a/packages/react/src/components/SignOutButton/index.ts b/packages/react/src/components/SignOutButton/index.ts index a1bdf38..cca0840 100644 --- a/packages/react/src/components/SignOutButton/index.ts +++ b/packages/react/src/components/SignOutButton/index.ts @@ -1 +1 @@ -export * from "./SignOutButton.js"; +export * from './SignOutButton.js' diff --git a/packages/react/src/components/styles.css.ts b/packages/react/src/components/styles.css.ts index bbb6b55..47422b8 100644 --- a/packages/react/src/components/styles.css.ts +++ b/packages/react/src/components/styles.css.ts @@ -1,29 +1,29 @@ -import { style } from "@vanilla-extract/css"; +import { style } from '@vanilla-extract/css' const resetBase = { border: 0, - borderColor: "#f1f1f1", - borderStyle: "solid", + borderColor: '#f1f1f1', + borderStyle: 'solid', borderWidth: 0, - boxSizing: "border-box", - fontSize: "100%", + boxSizing: 'border-box', + fontSize: '100%', margin: 0, padding: 0, - verticalAlign: "baseline", - WebkitFontSmoothing: "antialiased", - WebkitTapHighlightColor: "transparent", -} as const; + verticalAlign: 'baseline', + WebkitFontSmoothing: 'antialiased', + WebkitTapHighlightColor: 'transparent', +} as const const reset = { button: { ...resetBase, - appearance: "none", - background: "none", - border: "none", - cursor: "pointer", - textAlign: "left", + appearance: 'none', + background: 'none', + border: 'none', + cursor: 'pointer', + textAlign: 'left', }, -} as const; +} as const export const button = { ...reset.button, @@ -34,51 +34,51 @@ export const button = { borderRadius: 8, fontSize: 18, fontWeight: 600, - display: "flex", - alignItems: "center", -}; + display: 'flex', + alignItems: 'center', +} export const resetButton = style({ ...button, - backgroundColor: "transparent", -}); + backgroundColor: 'transparent', +}) export const primaryButton = style({ ...button, - backgroundColor: "#7C65C1", - color: "white", -}); + backgroundColor: '#7C65C1', + color: 'white', +}) export const secondaryButton = style({ ...button, - backgroundColor: "rgba(0, 0, 0, 0.03)", - borderColor: "rgba(210, 210, 210, 1)", - borderWidth: "1px solid", - color: "black", -}); + backgroundColor: 'rgba(0, 0, 0, 0.03)', + borderColor: 'rgba(210, 210, 210, 1)', + borderWidth: '1px solid', + color: 'black', +}) export const tertiaryButton = style({ ...button, - backgroundColor: "transparent", - color: "#7C65C1", -}); + backgroundColor: 'transparent', + color: '#7C65C1', +}) export const qrCodeContainer = style({ - borderColor: "rgba(229, 231, 235, 0.333)", + borderColor: 'rgba(229, 231, 235, 0.333)', padding: 24, - backgroundColor: "white", + backgroundColor: 'white', borderWidth: 1, - borderStyle: "solid", + borderStyle: 'solid', borderRadius: 12, -}); +}) export const qrCodeWrapper = style({ - userSelect: "none", -}); + userSelect: 'none', +}) export const qrCode = style({ - position: "relative", - display: "flex", + position: 'relative', + display: 'flex', height: 0, - justifyContent: "center", -}); + justifyContent: 'center', +}) diff --git a/packages/react/src/demo.tsx b/packages/react/src/demo.tsx index 0010322..52e2e20 100644 --- a/packages/react/src/demo.tsx +++ b/packages/react/src/demo.tsx @@ -1,14 +1,15 @@ -import { createRoot } from "react-dom/client"; -import { Demo } from "./exports/index.js"; -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { createRoot } from 'react-dom/client' +import { Demo } from './exports/index.js' -const domNode = document.getElementById("root"); -// biome-ignore lint/style/noNonNullAssertion: for demo purposes -const root = createRoot(domNode!); +const domNode = document.getElementById('root') +if (!domNode) throw new Error('no root node') -const queryClient = new QueryClient(); +const root = createRoot(domNode) + +const queryClient = new QueryClient() root.render( , -); +) diff --git a/packages/react/src/exports/actions.ts b/packages/react/src/exports/actions.ts index dba5f35..5af96e2 100644 --- a/packages/react/src/exports/actions.ts +++ b/packages/react/src/exports/actions.ts @@ -1,4 +1,4 @@ -export * from "../actions/createChannel.js"; -export * from "../actions/pollChannelTillCompleted.js"; -export * from "../actions/signIn.js"; -export * from "../actions/verifySiweMessage.js"; +export * from '../actions/createChannel.js' +export * from '../actions/pollChannelTillCompleted.js' +export * from '../actions/signIn.js' +export * from '../actions/verifySiweMessage.js' diff --git a/packages/react/src/exports/components.ts b/packages/react/src/exports/components.ts index 9b1d06c..60d6df6 100644 --- a/packages/react/src/exports/components.ts +++ b/packages/react/src/exports/components.ts @@ -1,5 +1,5 @@ -export * from "../components/AuthKitProvider/index.js"; -export * from "../components/Demo/index.js"; -export * from "../components/ProfileButton/index.js"; -export * from "../components/SignInButton/index.js"; -export * from "../components/QRCode.js"; +export * from '../components/AuthKitProvider/index.js' +export * from '../components/Demo/index.js' +export * from '../components/ProfileButton/index.js' +export * from '../components/SignInButton/index.js' +export * from '../components/QRCode.js' diff --git a/packages/react/src/exports/hooks.ts b/packages/react/src/exports/hooks.ts index 5c7c59a..207d5a1 100644 --- a/packages/react/src/exports/hooks.ts +++ b/packages/react/src/exports/hooks.ts @@ -1,7 +1,7 @@ -export * from "../hooks/useConfig.js"; -export * from "../hooks/useCreateChannel.js"; -export * from "../hooks/usePollChannelTillCompleted.js"; -export * from "../hooks/useProfile.js"; -export * from "../hooks/useSignIn.js"; -export * from "../hooks/useSignInMessage.js"; -export * from "../hooks/useVerifySiweMessage.js"; +export * from '../hooks/useConfig.js' +export * from '../hooks/useCreateChannel.js' +export * from '../hooks/usePollChannelTillCompleted.js' +export * from '../hooks/useProfile.js' +export * from '../hooks/useSignIn.js' +export * from '../hooks/useSignInMessage.js' +export * from '../hooks/useVerifySiweMessage.js' diff --git a/packages/react/src/exports/index.ts b/packages/react/src/exports/index.ts index 16a39e2..d7e85c5 100644 --- a/packages/react/src/exports/index.ts +++ b/packages/react/src/exports/index.ts @@ -1,3 +1,3 @@ -export * from "./actions.js"; -export * from "./components.js"; -export * from "./hooks.js"; +export * from './actions.js' +export * from './components.js' +export * from './hooks.js' diff --git a/packages/react/src/hooks/index.ts b/packages/react/src/hooks/index.ts index 3e2a333..e1c30f7 100644 --- a/packages/react/src/hooks/index.ts +++ b/packages/react/src/hooks/index.ts @@ -1,6 +1,6 @@ -export * from "./useCreateChannel.js"; -export * from "./useSignIn.js"; -export * from "./useSignInMessage.js"; -export * from "./useProfile.js"; -export * from "./useVerifySiweMessage.js"; -export * from "./usePollChannelTillCompleted.js"; +export * from './useCreateChannel.js' +export * from './useSignIn.js' +export * from './useSignInMessage.js' +export * from './useProfile.js' +export * from './useVerifySiweMessage.js' +export * from './usePollChannelTillCompleted.js' diff --git a/packages/react/src/hooks/useConfig.ts b/packages/react/src/hooks/useConfig.ts index 89c00bf..b29c03d 100644 --- a/packages/react/src/hooks/useConfig.ts +++ b/packages/react/src/hooks/useConfig.ts @@ -1,10 +1,11 @@ -"use client"; +'use client' -import { useContext } from "react"; -import { AuthKitContext } from "../components/AuthKitProvider/AuthKitProvider.js"; +import { useContext } from 'react' +import { AuthKitContext } from '../components/AuthKitProvider/AuthKitProvider.js' export function useConfig() { - const context = useContext(AuthKitContext); - if (!context) throw new Error("`useConfig` must be used within `AuthKitProvider`"); - return context.config; + const context = useContext(AuthKitContext) + if (!context) + throw new Error('`useConfig` must be used within `AuthKitProvider`') + return context.config } diff --git a/packages/react/src/hooks/useCreateChannel.ts b/packages/react/src/hooks/useCreateChannel.ts index 7d93980..caab85b 100644 --- a/packages/react/src/hooks/useCreateChannel.ts +++ b/packages/react/src/hooks/useCreateChannel.ts @@ -1,41 +1,49 @@ -"use client"; +'use client' -import { useConfig } from "./useConfig.js"; -import { structuralSharing, useQuery, type UseQueryReturnType } from "../types/query.js"; +import type { CreateChannelErrorType } from '../actions/createChannel.js' import { type CreateChannelData, type CreateChannelOptions, type CreateChannelQueryFnData, type CreateChannelQueryKey, createChannelQueryOptions, -} from "../query/createChannel.js"; -import { type CreateChannelErrorType } from "../actions/createChannel.js"; -import { type UnionEvaluate } from "../types/utils.js"; -import type { QueryParameter } from "../types/properties.js"; +} from '../query/createChannel.js' +import type { QueryParameter } from '../types/properties.js' +import { + type UseQueryReturnType, + structuralSharing, + useQuery, +} from '../types/query.js' +import type { UnionEvaluate } from '../types/utils.js' +import { useConfig } from './useConfig.js' -export type UseCreateChannelParameters = UnionEvaluate< - CreateChannelOptions & - QueryParameter ->; -export type UseCreateChannelReturnType = UseQueryReturnType< - selectData, - CreateChannelErrorType ->; +export type UseCreateChannelParameters = + UnionEvaluate< + CreateChannelOptions & + QueryParameter< + CreateChannelQueryFnData, + CreateChannelErrorType, + selectData, + CreateChannelQueryKey + > + > +export type UseCreateChannelReturnType = + UseQueryReturnType export function useCreateChannel( parameters: UseCreateChannelParameters = {}, ): UseCreateChannelReturnType { - const { query = {} } = parameters; - const config = useConfig(); + const { query = {} } = parameters + const config = useConfig() - const options = createChannelQueryOptions(config, parameters); + const options = createChannelQueryOptions(config, parameters) - const enabled = query.enabled ?? true; + const enabled = query.enabled ?? true return useQuery({ ...query, ...options, enabled, structuralSharing: query.structuralSharing ?? structuralSharing, - }); + }) } diff --git a/packages/react/src/hooks/useDetectClickOutside.ts b/packages/react/src/hooks/useDetectClickOutside.ts index 498c828..f20a844 100644 --- a/packages/react/src/hooks/useDetectClickOutside.ts +++ b/packages/react/src/hooks/useDetectClickOutside.ts @@ -1,19 +1,22 @@ -"use client"; +'use client' -import { type RefObject, useEffect } from "react"; +import { type RefObject, useEffect } from 'react' -export function useDetectClickOutside(ref: RefObject, callback: () => void) { +export function useDetectClickOutside( + ref: RefObject, + callback: () => void, +) { useEffect(() => { function handleClickOutside(event: MouseEvent) { if (ref.current && !ref.current.contains(event.target as Node)) { - callback(); + callback() } } - document.addEventListener("mousedown", handleClickOutside); + document.addEventListener('mousedown', handleClickOutside) return () => { - document.removeEventListener("mousedown", handleClickOutside); - }; - }, [ref, callback]); + document.removeEventListener('mousedown', handleClickOutside) + } + }, [ref, callback]) } -export default useDetectClickOutside; +export default useDetectClickOutside diff --git a/packages/react/src/hooks/usePollChannelTillCompleted.ts b/packages/react/src/hooks/usePollChannelTillCompleted.ts index c26b382..bf55e50 100644 --- a/packages/react/src/hooks/usePollChannelTillCompleted.ts +++ b/packages/react/src/hooks/usePollChannelTillCompleted.ts @@ -1,19 +1,25 @@ -"use client"; +'use client' -import { useConfig } from "./useConfig.js"; +import type { PollChannelTillCompletedErrorType } from '../actions/pollChannelTillCompleted.js' import { - type PollChannelTillCompletedOptions, type PollChannelTillCompletedData, + type PollChannelTillCompletedOptions, type PollChannelTillCompletedQueryFnData, type PollChannelTillCompletedQueryKey, pollChannelTillCompletedQueryOptions, -} from "../query/pollChannelTillCompleted.js"; -import { type UnionEvaluate } from "../types/utils.js"; -import { type QueryParameter } from "../types/properties.js"; -import { type PollChannelTillCompletedErrorType } from "../actions/pollChannelTillCompleted.js"; -import { type UseQueryReturnType, structuralSharing, useQuery } from "../types/query.js"; +} from '../query/pollChannelTillCompleted.js' +import type { QueryParameter } from '../types/properties.js' +import { + type UseQueryReturnType, + structuralSharing, + useQuery, +} from '../types/query.js' +import type { UnionEvaluate } from '../types/utils.js' +import { useConfig } from './useConfig.js' -export type UsePollChannelTillCompletedParameters = UnionEvaluate< +export type UsePollChannelTillCompletedParameters< + selectData = PollChannelTillCompletedData, +> = UnionEvaluate< PollChannelTillCompletedOptions & QueryParameter< PollChannelTillCompletedQueryFnData, @@ -21,27 +27,28 @@ export type UsePollChannelTillCompletedParameters ->; +> -export type UsePollChannelTillCompletedReturnType = UseQueryReturnType< - selectData, - PollChannelTillCompletedErrorType ->; +export type UsePollChannelTillCompletedReturnType< + selectData = PollChannelTillCompletedData, +> = UseQueryReturnType -export function usePollChannelTillCompleted( +export function usePollChannelTillCompleted< + selectData = PollChannelTillCompletedData, +>( parameters: UsePollChannelTillCompletedParameters = {}, ): UsePollChannelTillCompletedReturnType { - const { channelToken, query = {} } = parameters; - const config = useConfig(); + const { channelToken, query = {} } = parameters + const config = useConfig() - const options = pollChannelTillCompletedQueryOptions(config, parameters); + const options = pollChannelTillCompletedQueryOptions(config, parameters) - const enabled = Boolean(channelToken && (query.enabled ?? true)); + const enabled = Boolean(channelToken && (query.enabled ?? true)) return useQuery({ ...query, ...options, enabled, structuralSharing: query.structuralSharing ?? structuralSharing, - }); + }) } diff --git a/packages/react/src/hooks/useProfile.ts b/packages/react/src/hooks/useProfile.ts index 9190e0a..1275af7 100644 --- a/packages/react/src/hooks/useProfile.ts +++ b/packages/react/src/hooks/useProfile.ts @@ -1,23 +1,33 @@ -"use client"; +'use client' -import { type PollChannelTillCompletedReturnType } from "@fc-auth/core"; -import { create } from "zustand"; +import type { PollChannelTillCompletedReturnType } from '@fc-auth/core' +import { create } from 'zustand' export type Profile = Pick< PollChannelTillCompletedReturnType, - "fid" | "pfpUrl" | "username" | "displayName" | "bio" | "custody" | "verifications" -> & { isAuthenticated: boolean }; + | 'fid' + | 'pfpUrl' + | 'username' + | 'displayName' + | 'bio' + | 'custody' + | 'verifications' +> & { isAuthenticated: boolean } -type ProfileStore = { profile: Profile | undefined; set: (profile: Profile) => void; reset: () => void }; +type ProfileStore = { + profile: Profile | undefined + set: (profile: Profile) => void + reset: () => void +} export const useProfileStore = create((set) => ({ profile: undefined, set: (profile) => set({ profile }), reset: () => set({ profile: undefined }), -})); +})) export function useProfile() { - return useProfileStore(({ profile }) => profile); + return useProfileStore(({ profile }) => profile) } -export default useProfile; +export default useProfile diff --git a/packages/react/src/hooks/useSignIn.ts b/packages/react/src/hooks/useSignIn.ts index 0787191..360b6ad 100644 --- a/packages/react/src/hooks/useSignIn.ts +++ b/packages/react/src/hooks/useSignIn.ts @@ -1,54 +1,75 @@ -"use client"; +'use client' -import { useConfig } from "../hooks/useConfig.js"; -import { useMutation } from "@tanstack/react-query"; -import { useSignInMessageStore } from "./useSignInMessage.js"; -import { useProfileStore } from "./useProfile.js"; -import { useCallback, useEffect } from "react"; -import { type UseMutationParameters, type UseMutationReturnType } from "../types/query.js"; +import { useMutation } from '@tanstack/react-query' +import { useCallback, useEffect } from 'react' +import type { SignInErrorType } from '../actions/signIn.js' +import { useConfig } from '../hooks/useConfig.js' import { type SignInData, type SignInMutate, type SignInMutateAsync, type SignInVariables, signInOptions, -} from "../query/signIn.js"; -import { type SignInErrorType } from "../actions/signIn.js"; -import { type Evaluate } from "../types/utils.js"; +} from '../query/signIn.js' +import type { + UseMutationParameters, + UseMutationReturnType, +} from '../types/query.js' +import type { Evaluate } from '../types/utils.js' +import { useProfileStore } from './useProfile.js' +import { useSignInMessageStore } from './useSignInMessage.js' export type UseSignInParameters = { - mutation?: UseMutationParameters; -}; + mutation?: UseMutationParameters< + SignInData, + SignInErrorType, + SignInVariables, + context + > +} export type UseSignInReturnType = Evaluate< - UseMutationReturnType & { - signIn: SignInMutate; - signInAsync: SignInMutateAsync; - signOut: () => void; + UseMutationReturnType< + SignInData, + SignInErrorType, + SignInVariables, + context + > & { + signIn: SignInMutate + signInAsync: SignInMutateAsync + signOut: () => void } ->; +> export function useSignIn({ mutation, }: UseSignInParameters = {}): UseSignInReturnType { - const config = useConfig(); + const config = useConfig() - const { setProfile, resetProfile } = useProfileStore(({ set, reset }) => ({ setProfile: set, resetProfile: reset })); - const { setSignInMessage, resetSignInMessage } = useSignInMessageStore(({ set, reset }) => ({ - setSignInMessage: set, - resetSignInMessage: reset, - })); + const { setProfile, resetProfile } = useProfileStore(({ set, reset }) => ({ + setProfile: set, + resetProfile: reset, + })) + const { setSignInMessage, resetSignInMessage } = useSignInMessageStore( + ({ set, reset }) => ({ + setSignInMessage: set, + resetSignInMessage: reset, + }), + ) - const mutationOptions = signInOptions(config); + const mutationOptions = signInOptions(config) const { mutate, mutateAsync, ...result } = useMutation({ ...mutation, ...mutationOptions, - }); + }) useEffect(() => { - if (result.status !== "success") return; + if (result.status !== 'success') return - setSignInMessage({ message: result.data.message, signature: result.data.signature }); + setSignInMessage({ + message: result.data.message, + signature: result.data.signature, + }) setProfile({ isAuthenticated: result.data.isAuthenticated, fid: result.data.fid, @@ -58,16 +79,16 @@ export function useSignIn({ bio: result.data.bio, custody: result.data.custody, verifications: result.data.verifications, - }); - }, [setSignInMessage, setProfile, result.data, result.status]); + }) + }, [setSignInMessage, setProfile, result.data, result.status]) const signOut = useCallback(() => { - result.reset(); - resetProfile(); - resetSignInMessage(); - }, [result.reset, resetProfile, resetSignInMessage]); + result.reset() + resetProfile() + resetSignInMessage() + }, [result.reset, resetProfile, resetSignInMessage]) - return { signIn: mutate, signInAsync: mutateAsync, signOut, ...result }; + return { signIn: mutate, signInAsync: mutateAsync, signOut, ...result } } -export default useSignIn; +export default useSignIn diff --git a/packages/react/src/hooks/useSignInMessage.ts b/packages/react/src/hooks/useSignInMessage.ts index e651d0b..5d1341c 100644 --- a/packages/react/src/hooks/useSignInMessage.ts +++ b/packages/react/src/hooks/useSignInMessage.ts @@ -1,23 +1,29 @@ -"use client"; +'use client' -import { create } from "zustand"; +import { create } from 'zustand' type SignInMessageStore = { - message: string | undefined; - signature: `0x${string}` | undefined; - set: ({ message, signature }: { message: string; signature: `0x${string}` }) => void; - reset: () => void; -}; + message: string | undefined + signature: `0x${string}` | undefined + set: ({ + message, + signature, + }: { message: string; signature: `0x${string}` }) => void + reset: () => void +} export const useSignInMessageStore = create((set) => ({ message: undefined, signature: undefined, set: ({ message, signature }) => set({ message, signature }), reset: () => set({ message: undefined }), -})); +})) export function useSignInMessage() { - return useSignInMessageStore(({ message, signature }) => ({ message, signature })); + return useSignInMessageStore(({ message, signature }) => ({ + message, + signature, + })) } -export default useSignInMessage; +export default useSignInMessage diff --git a/packages/react/src/hooks/useVerifySiweMessage.ts b/packages/react/src/hooks/useVerifySiweMessage.ts index b86304e..67fbf8c 100644 --- a/packages/react/src/hooks/useVerifySiweMessage.ts +++ b/packages/react/src/hooks/useVerifySiweMessage.ts @@ -1,17 +1,20 @@ -"use client"; +'use client' -import { useConfig } from "./useConfig.js"; -import { useMutation } from "@tanstack/react-query"; -import { type UseMutationParameters, type UseMutationReturnType } from "../types/query.js"; +import { useMutation } from '@tanstack/react-query' +import type { VerifySiweMessageErrorType } from '../actions/verifySiweMessage.js' import { type VerifySiweMessageData, type VerifySiweMessageMutate, type VerifySiweMessageMutateAsync, type VerifySiweMessageVariables, verifySiweMessageOptions, -} from "../query/verifySiweMessage.js"; -import { type VerifySiweMessageErrorType } from "../actions/verifySiweMessage.js"; -import { type Evaluate } from "../types/utils.js"; +} from '../query/verifySiweMessage.js' +import type { + UseMutationParameters, + UseMutationReturnType, +} from '../types/query.js' +import type { Evaluate } from '../types/utils.js' +import { useConfig } from './useConfig.js' export type UseVerifySiweMessageParameters = { mutation?: UseMutationParameters< @@ -19,26 +22,35 @@ export type UseVerifySiweMessageParameters = { VerifySiweMessageErrorType, VerifySiweMessageVariables, context - >; -}; + > +} export type UseVerifySiweMessageReturnType = Evaluate< - UseMutationReturnType & { - verifySiweMessage: VerifySiweMessageMutate; - verifySiweMessageAsync: VerifySiweMessageMutateAsync; + UseMutationReturnType< + VerifySiweMessageData, + VerifySiweMessageErrorType, + VerifySiweMessageVariables, + context + > & { + verifySiweMessage: VerifySiweMessageMutate + verifySiweMessageAsync: VerifySiweMessageMutateAsync } ->; +> export function useVerifySiweMessage({ mutation, }: UseVerifySiweMessageParameters = {}): UseVerifySiweMessageReturnType { - const config = useConfig(); + const config = useConfig() - const mutationOptions = verifySiweMessageOptions(config); + const mutationOptions = verifySiweMessageOptions(config) const { mutate, mutateAsync, ...result } = useMutation({ ...mutation, ...mutationOptions, - }); + }) - return { verifySiweMessage: mutate, verifySiweMessageAsync: mutateAsync, ...result }; + return { + verifySiweMessage: mutate, + verifySiweMessageAsync: mutateAsync, + ...result, + } } diff --git a/packages/react/src/query/createChannel.ts b/packages/react/src/query/createChannel.ts index f9e1d08..ffbcf0d 100644 --- a/packages/react/src/query/createChannel.ts +++ b/packages/react/src/query/createChannel.ts @@ -1,42 +1,48 @@ import { + type CreateChannelErrorType, type CreateChannelParameters, type CreateChannelReturnType, - type CreateChannelErrorType, createChannel, -} from "../actions/createChannel.js"; -import { type UnionPartial } from "../types/utils.js"; -import { type Config } from "../types/config.js"; -import type { ScopeKeyParameter } from "../types/properties.js"; -import { filterQueryOptions } from "./utils.js"; -import type { QueryOptions } from "../types/query.js"; +} from '../actions/createChannel.js' +import type { Config } from '../types/config.js' +import type { ScopeKeyParameter } from '../types/properties.js' +import type { QueryOptions } from '../types/query.js' +import type { UnionPartial } from '../types/utils.js' +import { filterQueryOptions } from './utils.js' -export type CreateChannelOptions = UnionPartial & ScopeKeyParameter; +export type CreateChannelOptions = UnionPartial & + ScopeKeyParameter export function createChannelQueryOptions( config: Config, options: CreateChannelOptions, -): QueryOptions { +): QueryOptions< + CreateChannelQueryFnData, + CreateChannelErrorType, + CreateChannelData, + CreateChannelQueryKey +> { return { // TODO: Support `signal` // https://tkdodo.eu/blog/why-you-want-react-query#bonus-cancellation async queryFn({ queryKey }) { - const { scopeKey: _, siweUri, domain, ...args } = queryKey[1]; - if (!siweUri || !domain) throw new Error("missing siweUri or domain"); - return createChannel(config, { siweUri, domain, ...args }); + const { scopeKey: _, siweUri, domain, ...args } = queryKey[1] + if (!siweUri || !domain) throw new Error('missing siweUri or domain') + return createChannel(config, { siweUri, domain, ...args }) }, queryKey: createChannelQueryKey({ ...options, siweUri: options.siweUri ?? config.siweUri, domain: options.domain ?? config.domain, }), - } as const; + } as const } -export type CreateChannelQueryFnData = CreateChannelReturnType; +export type CreateChannelQueryFnData = CreateChannelReturnType -export type CreateChannelData = CreateChannelQueryFnData; +export type CreateChannelData = CreateChannelQueryFnData export function createChannelQueryKey(options: CreateChannelOptions = {}) { - return ["createChannel", filterQueryOptions(options)] as const; + return ['createChannel', filterQueryOptions(options)] as const } -export type CreateChannelQueryKey = ReturnType; +export type CreateChannelQueryKey = ReturnType diff --git a/packages/react/src/query/pollChannelTillCompleted.ts b/packages/react/src/query/pollChannelTillCompleted.ts index 152674c..7a7273a 100644 --- a/packages/react/src/query/pollChannelTillCompleted.ts +++ b/packages/react/src/query/pollChannelTillCompleted.ts @@ -1,16 +1,17 @@ import { + type PollChannelTillCompletedErrorType, type PollChannelTillCompletedParameters, type PollChannelTillCompletedReturnType, - type PollChannelTillCompletedErrorType, pollChannelTillCompleted, -} from "../actions/pollChannelTillCompleted.js"; -import { type Config } from "../types/config.js"; -import { filterQueryOptions } from "./utils.js"; -import { type ScopeKeyParameter } from "../types/properties.js"; -import { type UnionPartial } from "../types/utils.js"; -import type { QueryOptions } from "../types/query.js"; +} from '../actions/pollChannelTillCompleted.js' +import type { Config } from '../types/config.js' +import type { ScopeKeyParameter } from '../types/properties.js' +import type { QueryOptions } from '../types/query.js' +import type { UnionPartial } from '../types/utils.js' +import { filterQueryOptions } from './utils.js' -export type PollChannelTillCompletedOptions = UnionPartial & ScopeKeyParameter; +export type PollChannelTillCompletedOptions = + UnionPartial & ScopeKeyParameter export function pollChannelTillCompletedQueryOptions( config: Config, @@ -25,20 +26,29 @@ export function pollChannelTillCompletedQueryOptions( // TODO: Support `signal` // https://tkdodo.eu/blog/why-you-want-react-query#bonus-cancellation async queryFn({ queryKey }) { - const { scopeKey: _, channelToken, timeout, interval } = queryKey[1]; - if (!channelToken) throw new Error("channelToken is required"); - return pollChannelTillCompleted(config, { channelToken, timeout, interval }); + const { scopeKey: _, channelToken, timeout, interval } = queryKey[1] + if (!channelToken) throw new Error('channelToken is required') + return pollChannelTillCompleted(config, { + channelToken, + timeout, + interval, + }) }, queryKey: pollChannelTillCompletedQueryKey(options), - } as const; + } as const } -export type PollChannelTillCompletedQueryFnData = PollChannelTillCompletedReturnType; +export type PollChannelTillCompletedQueryFnData = + PollChannelTillCompletedReturnType -export type PollChannelTillCompletedData = PollChannelTillCompletedQueryFnData; +export type PollChannelTillCompletedData = PollChannelTillCompletedQueryFnData -export function pollChannelTillCompletedQueryKey(options: PollChannelTillCompletedOptions = {}) { - return ["pollChannelTillCompleted", filterQueryOptions(options)] as const; +export function pollChannelTillCompletedQueryKey( + options: PollChannelTillCompletedOptions = {}, +) { + return ['pollChannelTillCompleted', filterQueryOptions(options)] as const } -export type PollChannelTillCompletedQueryKey = ReturnType; +export type PollChannelTillCompletedQueryKey = ReturnType< + typeof pollChannelTillCompletedQueryKey +> diff --git a/packages/react/src/query/signIn.ts b/packages/react/src/query/signIn.ts index bb2726f..8ebc71b 100644 --- a/packages/react/src/query/signIn.ts +++ b/packages/react/src/query/signIn.ts @@ -1,28 +1,40 @@ -import type { MutateOptions, MutationOptions } from "@tanstack/query-core"; +import type { MutateOptions, MutationOptions } from '@tanstack/query-core' -import { type SignInParameters, type SignInReturnType, signIn } from "../actions/signIn.js"; -import { type Evaluate } from "../types/utils.js"; -import { type Config } from "../types/config.js"; +import { + type SignInParameters, + type SignInReturnType, + signIn, +} from '../actions/signIn.js' +import type { Config } from '../types/config.js' +import type { Evaluate } from '../types/utils.js' export function signInOptions(config: Config) { return { mutationFn(variables) { - return signIn(config, variables); + return signIn(config, variables) }, - mutationKey: ["signIn"], - } as const satisfies MutationOptions; + mutationKey: ['signIn'], + } as const satisfies MutationOptions } -export type SignInData = Evaluate; +export type SignInData = Evaluate -export type SignInVariables = SignInParameters; +export type SignInVariables = SignInParameters export type SignInMutate = ( variables: SignInVariables, - options?: Evaluate, context>> | undefined, -) => void; + options?: + | Evaluate< + MutateOptions, context> + > + | undefined, +) => void export type SignInMutateAsync = ( variables: SignInVariables, - options?: Evaluate, context>> | undefined, -) => Promise; + options?: + | Evaluate< + MutateOptions, context> + > + | undefined, +) => Promise diff --git a/packages/react/src/query/utils.ts b/packages/react/src/query/utils.ts index 162bf4c..e908cf0 100644 --- a/packages/react/src/query/utils.ts +++ b/packages/react/src/query/utils.ts @@ -1,5 +1,7 @@ // Modified from https://github.com/wevm/wagmi/blob/7e60b5e50abe5a89bdbac0cdd78d8327178d0021/packages/core/src/query/utils.ts -export function filterQueryOptions>(options: type): type { +export function filterQueryOptions>( + options: type, +): type { // destructuring is super fast // biome-ignore format: no formatting const { @@ -15,5 +17,5 @@ export function filterQueryOptions>(options ...rest } = options - return rest as type; + return rest as type } diff --git a/packages/react/src/query/verifySiweMessage.ts b/packages/react/src/query/verifySiweMessage.ts index edf2d8e..91a0ac3 100644 --- a/packages/react/src/query/verifySiweMessage.ts +++ b/packages/react/src/query/verifySiweMessage.ts @@ -1,36 +1,54 @@ -import type { MutateOptions, MutationOptions } from "@tanstack/query-core"; +import type { MutateOptions, MutationOptions } from '@tanstack/query-core' import { type VerifySiweMessageParameters, type VerifySiweMessageReturnType, verifySiweMessage, -} from "../actions/verifySiweMessage.js"; -import { type Evaluate } from "../types/utils.js"; -import { type Config } from "../types/config.js"; +} from '../actions/verifySiweMessage.js' +import type { Config } from '../types/config.js' +import type { Evaluate } from '../types/utils.js' export function verifySiweMessageOptions(config: Config) { return { mutationFn(variables) { - return verifySiweMessage(config, variables); + return verifySiweMessage(config, variables) }, - mutationKey: ["verifySiweMessage"], - } as const satisfies MutationOptions; + mutationKey: ['verifySiweMessage'], + } as const satisfies MutationOptions< + VerifySiweMessageData, + Error, + VerifySiweMessageVariables + > } -export type VerifySiweMessageData = Evaluate; +export type VerifySiweMessageData = Evaluate -export type VerifySiweMessageVariables = VerifySiweMessageParameters; +export type VerifySiweMessageVariables = VerifySiweMessageParameters export type VerifySiweMessageMutate = ( variables: VerifySiweMessageVariables, options?: - | Evaluate, context>> + | Evaluate< + MutateOptions< + VerifySiweMessageData, + Error, + Evaluate, + context + > + > | undefined, -) => void; +) => void export type VerifySiweMessageMutateAsync = ( variables: VerifySiweMessageVariables, options?: - | Evaluate, context>> + | Evaluate< + MutateOptions< + VerifySiweMessageData, + Error, + Evaluate, + context + > + > | undefined, -) => Promise; +) => Promise diff --git a/packages/react/src/types/config.ts b/packages/react/src/types/config.ts index 8f7fc8c..942478f 100644 --- a/packages/react/src/types/config.ts +++ b/packages/react/src/types/config.ts @@ -1,13 +1,13 @@ -import { type AppClient } from "@fc-auth/core"; -import { type Provider } from "ethers"; +import type { AppClient } from '@fc-auth/core' +import type { Provider } from 'ethers' export interface Config { - relay?: string; - domain: string; - siweUri: string; - rpcUrl?: string; - redirectUrl?: string; - version?: string; - appClient: AppClient; - provider?: Provider; + relay?: string + domain: string + siweUri: string + rpcUrl?: string + redirectUrl?: string + version?: string + appClient: AppClient + provider?: Provider } diff --git a/packages/react/src/types/deepEqual.ts b/packages/react/src/types/deepEqual.ts index a8bc23b..89b47a6 100644 --- a/packages/react/src/types/deepEqual.ts +++ b/packages/react/src/types/deepEqual.ts @@ -1,41 +1,43 @@ /** Forked from https://github.com/epoberezkin/fast-deep-equal */ export function deepEqual(a: any, b: any) { - if (a === b) return true; + if (a === b) return true - if (a && b && typeof a === "object" && typeof b === "object") { - if (a.constructor !== b.constructor) return false; + if (a && b && typeof a === 'object' && typeof b === 'object') { + if (a.constructor !== b.constructor) return false - let length: number; - let i: number; + let length: number + let i: number if (Array.isArray(a) && Array.isArray(b)) { - length = a.length; - if (length !== b.length) return false; - for (i = length; i-- !== 0; ) if (!deepEqual(a[i], b[i])) return false; - return true; + length = a.length + if (length !== b.length) return false + for (i = length; i-- !== 0; ) if (!deepEqual(a[i], b[i])) return false + return true } - if (a.valueOf !== Object.prototype.valueOf) return a.valueOf() === b.valueOf(); - if (a.toString !== Object.prototype.toString) return a.toString() === b.toString(); + if (a.valueOf !== Object.prototype.valueOf) + return a.valueOf() === b.valueOf() + if (a.toString !== Object.prototype.toString) + return a.toString() === b.toString() - const keys = Object.keys(a); - length = keys.length; - if (length !== Object.keys(b).length) return false; + const keys = Object.keys(a) + length = keys.length + if (length !== Object.keys(b).length) return false - // biome-ignore lint/style/noNonNullAssertion: - for (i = length; i-- !== 0; ) if (!Object.prototype.hasOwnProperty.call(b, keys[i]!)) return false; + for (i = length; i-- !== 0; ) + if (!Object.prototype.hasOwnProperty.call(b, keys[i]!)) return false for (i = length; i-- !== 0; ) { - const key = keys[i]; + const key = keys[i] - if (key && !deepEqual(a[key], b[key])) return false; + if (key && !deepEqual(a[key], b[key])) return false } - return true; + return true } // true if both NaN, false otherwise // biome-ignore lint/suspicious/noSelfCompare: - return a !== a && b !== b; + return a !== a && b !== b } diff --git a/packages/react/src/types/properties.ts b/packages/react/src/types/properties.ts index bfc3a70..0c80187 100644 --- a/packages/react/src/types/properties.ts +++ b/packages/react/src/types/properties.ts @@ -1,12 +1,12 @@ -import { type DefaultError, type QueryKey } from "@tanstack/react-query"; -import { type UseQueryParameters } from "./query.js"; -import { type Omit } from "./utils.js"; +import type { DefaultError, QueryKey } from '@tanstack/react-query' +import type { UseQueryParameters } from './query.js' +import type { Omit } from './utils.js' export type EnabledParameter = { - enabled?: boolean | undefined; -}; + enabled?: boolean | undefined +} -export type ScopeKeyParameter = { scopeKey?: string | undefined }; +export type ScopeKeyParameter = { scopeKey?: string | undefined } export type QueryParameter< queryFnData = unknown, @@ -17,7 +17,7 @@ export type QueryParameter< query?: | Omit< UseQueryParameters, - "queryFn" | "queryHash" | "queryKey" | "queryKeyHashFn" | "throwOnError" + 'queryFn' | 'queryHash' | 'queryKey' | 'queryKeyHashFn' | 'throwOnError' > - | undefined; -}; + | undefined +} diff --git a/packages/react/src/types/query.ts b/packages/react/src/types/query.ts index bc7e3c1..f1bd0ce 100644 --- a/packages/react/src/types/query.ts +++ b/packages/react/src/types/query.ts @@ -7,13 +7,13 @@ import { type UseMutationResult, type UseQueryOptions, type UseQueryResult, - type QueryOptions as tanstack_QueryOptions, replaceEqualDeep, + type QueryOptions as tanstack_QueryOptions, useInfiniteQuery as tanstack_useInfiniteQuery, useQuery as tanstack_useQuery, -} from "@tanstack/react-query"; -import { type Evaluate, type ExactPartial, type Omit, type UnionOmit } from "./utils.js"; -import { deepEqual } from "./deepEqual.js"; +} from '@tanstack/react-query' +import { deepEqual } from './deepEqual.js' +import type { Evaluate, ExactPartial, Omit, UnionOmit } from './utils.js' export function hashFn(queryKey: QueryKey): string { return JSON.stringify(queryKey, (_, value) => { @@ -21,46 +21,62 @@ export function hashFn(queryKey: QueryKey): string { return Object.keys(value) .sort() .reduce((result, key) => { - result[key] = value[key]; - return result; - }, {} as any); - if (typeof value === "bigint") return value.toString(); - return value; - }); + result[key] = value[key] + return result + }, {} as any) + if (typeof value === 'bigint') return value.toString() + return value + }) } function isPlainObject(o: any): o is Object { if (!hasObjectPrototype(o)) { - return false; + return false } // If has modified constructor - const ctor = o.constructor; - if (typeof ctor === "undefined") return true; + const ctor = o.constructor + if (typeof ctor === 'undefined') return true // If has modified prototype - const prot = ctor.prototype; - if (!hasObjectPrototype(prot)) return false; + const prot = ctor.prototype + if (!hasObjectPrototype(prot)) return false // If constructor does not have an Object-specific method // biome-ignore lint/suspicious/noPrototypeBuiltins: - if (!prot.hasOwnProperty("isPrototypeOf")) return false; + if (!prot.hasOwnProperty('isPrototypeOf')) return false // Most likely a plain Object - return true; + return true } function hasObjectPrototype(o: any): boolean { - return Object.prototype.toString.call(o) === "[object Object]"; + return Object.prototype.toString.call(o) === '[object Object]' } -export type UseMutationParameters = Evaluate< - Omit, context>, "mutationFn" | "mutationKey" | "throwOnError"> ->; - -export type UseMutationReturnType = Evaluate< - UnionOmit, "mutate" | "mutateAsync"> ->; +export type UseMutationParameters< + data = unknown, + error = Error, + variables = void, + context = unknown, +> = Evaluate< + Omit< + UseMutationOptions, context>, + 'mutationFn' | 'mutationKey' | 'throwOnError' + > +> + +export type UseMutationReturnType< + data = unknown, + error = Error, + variables = void, + context = unknown, +> = Evaluate< + UnionOmit< + UseMutationResult, + 'mutate' | 'mutateAsync' + > +> //////////////////////////////////////////////////////////////////////////////// @@ -70,32 +86,36 @@ export type UseQueryParameters< data = queryFnData, queryKey extends QueryKey = QueryKey, > = Evaluate< - ExactPartial, "initialData">> & { + ExactPartial< + Omit, 'initialData'> + > & { // Fix `initialData` type - initialData?: UseQueryOptions["initialData"] | undefined; + initialData?: + | UseQueryOptions['initialData'] + | undefined } ->; +> export type UseQueryReturnType = Evaluate< UseQueryResult & { - queryKey: QueryKey; + queryKey: QueryKey } ->; +> // Adding some basic customization. // Ideally we don't have this function, but `import('@tanstack/react-query').useQuery` currently has some quirks where it is super hard to // pass down the inferred `initialData` type because of it's discriminated overload in the on `useQuery`. export function useQuery( parameters: UseQueryParameters & { - queryKey: QueryKey; + queryKey: QueryKey }, ): UseQueryReturnType { const result = tanstack_useQuery({ ...(parameters as any), queryKeyHashFn: hashFn, // for bigint support - }) as UseQueryReturnType; - result.queryKey = parameters.queryKey; - return result; + }) as UseQueryReturnType + result.queryKey = parameters.queryKey + return result } export type QueryOptions< @@ -105,9 +125,23 @@ export type QueryOptions< TQueryKey extends QueryKey = QueryKey, TPageParam = never, > = Evaluate< - Omit, "queryKey"> & - Required, "queryKey">> ->; + Omit< + tanstack_QueryOptions, + 'queryKey' + > & + Required< + Pick< + tanstack_QueryOptions< + TQueryFnData, + TError, + TData, + TQueryKey, + TPageParam + >, + 'queryKey' + > + > +> //////////////////////////////////////////////////////////////////////////////// @@ -119,33 +153,61 @@ export type UseInfiniteQueryParameters< queryKey extends QueryKey = QueryKey, pageParam = unknown, > = Evaluate< - Omit, "initialData"> & { + Omit< + UseInfiniteQueryOptions< + queryFnData, + error, + data, + queryData, + queryKey, + pageParam + >, + 'initialData' + > & { // Fix `initialData` type - initialData?: UseInfiniteQueryOptions["initialData"] | undefined; + initialData?: + | UseInfiniteQueryOptions< + queryFnData, + error, + data, + queryKey + >['initialData'] + | undefined } ->; +> -export type UseInfiniteQueryReturnType = UseInfiniteQueryResult & { - queryKey: QueryKey; -}; +export type UseInfiniteQueryReturnType< + data = unknown, + error = DefaultError, +> = UseInfiniteQueryResult & { + queryKey: QueryKey +} // Adding some basic customization. -export function useInfiniteQuery( +export function useInfiniteQuery< + queryFnData, + error, + data, + queryKey extends QueryKey, +>( parameters: UseInfiniteQueryParameters & { - queryKey: QueryKey; + queryKey: QueryKey }, ): UseInfiniteQueryReturnType { const result = tanstack_useInfiniteQuery({ ...(parameters as any), queryKeyHashFn: hashFn, // for bigint support - }) as UseInfiniteQueryReturnType; - result.queryKey = parameters.queryKey; - return result; + }) as UseInfiniteQueryReturnType + result.queryKey = parameters.queryKey + return result } //////////////////////////////////////////////////////////////////////////////// -export function structuralSharing(oldData: data | undefined, newData: data): data { - if (deepEqual(oldData, newData)) return oldData as data; - return replaceEqualDeep(oldData, newData); +export function structuralSharing( + oldData: data | undefined, + newData: data, +): data { + if (deepEqual(oldData, newData)) return oldData as data + return replaceEqualDeep(oldData, newData) } diff --git a/packages/react/src/types/utils.ts b/packages/react/src/types/utils.ts index 1689739..6a09933 100644 --- a/packages/react/src/types/utils.ts +++ b/packages/react/src/types/utils.ts @@ -1,7 +1,7 @@ // Copied from https://github.com/wevm/wagmi/blob/main/packages/core/src/types/utils.ts /** Combines members of an intersection into a readable type. */ // https://twitter.com/mattpocockuk/status/1622730173446557697?s=20&t=NdpAcmEFXY01xkqU3KO0Mg -export type Evaluate = { [key in keyof type]: type[key] } & unknown; +export type Evaluate = { [key in keyof type]: type[key] } & unknown /** * Makes all properties of an object optional. @@ -9,68 +9,87 @@ export type Evaluate = { [key in keyof type]: type[key] } & unknown; * Compatible with [`exactOptionalPropertyTypes`](https://www.typescriptlang.org/tsconfig#exactOptionalPropertyTypes). */ export type ExactPartial = { - [key in keyof type]?: type[key] | undefined; -}; + [key in keyof type]?: type[key] | undefined +} /** Checks if {@link type} can be narrowed further than {@link type2} */ export type IsNarrowable = IsUnknown extends true ? false : undefined extends type - ? false - : IsNever<(type extends type2 ? true : false) & (type2 extends type ? false : true)> extends true - ? false - : true; + ? false + : IsNever< + (type extends type2 ? true : false) & + (type2 extends type ? false : true) + > extends true + ? false + : true /** * @internal * Checks if {@link type} is `never` */ -export type IsNever = [type] extends [never] ? true : false; +export type IsNever = [type] extends [never] ? true : false /** * @internal * Checks if {@link type} is `unknown` */ -export type IsUnknown = unknown extends type ? true : false; +export type IsUnknown = unknown extends type ? true : false /** Merges two object types into new type */ export type Merge = Evaluate< - LooseOmit & obj2 ->; + LooseOmit & + obj2 +> /** Removes `readonly` from all properties of an object. */ export type Mutable = { - -readonly [key in keyof type]: type[key]; -}; + -readonly [key in keyof type]: type[key] +} /** Strict version of built-in Omit type */ -export type Omit = Pick>; +export type Omit = Pick< + type, + Exclude +> /** Makes objects destructurable. */ export type OneOf< union extends object, /// keys extends KeyofUnion = KeyofUnion, -> = union extends infer Item ? Evaluate]?: undefined }> : never; -type KeyofUnion = type extends type ? keyof type : never; +> = union extends infer Item + ? Evaluate]?: undefined }> + : never +type KeyofUnion = type extends type ? keyof type : never /** Makes {@link key} optional in {@link type} while preserving type inference. */ // s/o trpc (https://github.com/trpc/trpc/blob/main/packages/server/src/types.ts#L6) -export type PartialBy = ExactPartial> & Omit; +export type PartialBy = ExactPartial< + Pick +> & + Omit /////////////////////////////////////////////////////////////////////////// // Loose types /** Loose version of {@link Omit} */ -export type LooseOmit = Pick>; +export type LooseOmit = Pick< + type, + Exclude +> /////////////////////////////////////////////////////////////////////////// // Union types -export type UnionEvaluate = type extends object ? Evaluate : type; +export type UnionEvaluate = type extends object ? Evaluate : type -export type UnionLooseOmit = type extends any ? LooseOmit : never; +export type UnionLooseOmit = type extends any + ? LooseOmit + : never -export type UnionOmit = type extends any ? Omit : never; +export type UnionOmit = type extends any + ? Omit + : never -export type UnionPartial = type extends object ? ExactPartial : type; +export type UnionPartial = type extends object ? ExactPartial : type diff --git a/packages/react/src/utils.ts b/packages/react/src/utils.ts index 783c700..8aecd58 100644 --- a/packages/react/src/utils.ts +++ b/packages/react/src/utils.ts @@ -1,24 +1,29 @@ export function isAndroid(): boolean { - return typeof navigator !== "undefined" && /android/i.test(navigator.userAgent); + return ( + typeof navigator !== 'undefined' && /android/i.test(navigator.userAgent) + ) } export function isSmallIOS(): boolean { - return typeof navigator !== "undefined" && /iPhone|iPod/.test(navigator.userAgent); + return ( + typeof navigator !== 'undefined' && /iPhone|iPod/.test(navigator.userAgent) + ) } export function isLargeIOS(): boolean { return ( - typeof navigator !== "undefined" && - (/iPad/.test(navigator.userAgent) || (navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1)) - ); + typeof navigator !== 'undefined' && + (/iPad/.test(navigator.userAgent) || + (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1)) + ) } export function isIOS(): boolean { - return isSmallIOS() || isLargeIOS(); + return isSmallIOS() || isLargeIOS() } export function isMobile(): boolean { - return isAndroid() || isIOS(); + return isAndroid() || isIOS() } -export type MaybePromise = T | Promise; +export type MaybePromise = T | Promise diff --git a/packages/react/turbo.json b/packages/react/turbo.json index 0d8e5c5..9877c79 100644 --- a/packages/react/turbo.json +++ b/packages/react/turbo.json @@ -4,7 +4,12 @@ "pipeline": { "build": { "dependsOn": ["@fc-auth/core#build"], - "inputs": ["src/**", "vite.config.ts", "tsconfig.json", "tsconfig.node.json"], + "inputs": [ + "src/**", + "vite.config.ts", + "tsconfig.json", + "tsconfig.node.json" + ], "outputs": ["dist/**"] } } diff --git a/packages/react/vite.config.ts b/packages/react/vite.config.ts index cb9f904..45a30f6 100644 --- a/packages/react/vite.config.ts +++ b/packages/react/vite.config.ts @@ -1,27 +1,38 @@ -import { resolve } from "path"; -import { defineConfig } from "vite"; -import react from "@vitejs/plugin-react"; -import { vanillaExtractPlugin } from "@vanilla-extract/vite-plugin"; -import dts from "vite-plugin-dts"; -import { nodePolyfills } from "vite-plugin-node-polyfills"; +import { resolve } from 'node:path' +import { vanillaExtractPlugin } from '@vanilla-extract/vite-plugin' +import react from '@vitejs/plugin-react' +import { defineConfig } from 'vite' +import dts from 'vite-plugin-dts' +import { nodePolyfills } from 'vite-plugin-node-polyfills' // https://vitejs.dev/config/ export default defineConfig({ - plugins: [react(), vanillaExtractPlugin(), dts(/* { rollupTypes: true } */), nodePolyfills({ include: ["buffer"] })], + plugins: [ + react(), + vanillaExtractPlugin(), + dts(/* { rollupTypes: true } */), + nodePolyfills({ include: ['buffer'] }), + ], build: { // target: "esnext", minify: false, lib: { - formats: ["es"], + formats: ['es'], entry: { - "exports/index": resolve(__dirname, "src/exports/index.ts"), - "exports/actions": resolve(__dirname, "src/exports/actions.ts"), - "exports/components": resolve(__dirname, "src/exports/components.ts"), - "exports/hooks": resolve(__dirname, "src/exports/hooks.ts"), + 'exports/index': resolve(__dirname, 'src/exports/index.ts'), + 'exports/actions': resolve(__dirname, 'src/exports/actions.ts'), + 'exports/components': resolve(__dirname, 'src/exports/components.ts'), + 'exports/hooks': resolve(__dirname, 'src/exports/hooks.ts'), }, }, rollupOptions: { - external: ["react", "react/jsx-runtime", "react-dom", "@fc-auth/core", "@tanstack/react-query"], + external: [ + 'react', + 'react/jsx-runtime', + 'react-dom', + '@fc-auth/core', + '@tanstack/react-query', + ], }, }, -}); +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 140ad78..0671276 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,8 +9,8 @@ importers: .: devDependencies: '@biomejs/biome': - specifier: ~1.1.2 - version: 1.1.2 + specifier: ~1.8.0 + version: 1.8.0 '@changesets/changelog-github': specifier: 0.4.6 version: 0.4.6 @@ -32,6 +32,9 @@ importers: '@types/node': specifier: ^20.12.12 version: 20.14.2 + fast-glob: + specifier: ^3.3.2 + version: 3.3.2 husky: specifier: ^8.0.3 version: 8.0.3 @@ -1857,68 +1860,88 @@ packages: resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} dev: true - /@biomejs/biome@1.1.2: - resolution: {integrity: sha512-JEVWchqo0Xhl86IJgOh0xESWnNRUXBUDByCBR8TA4lIPzm/6U6Tv77+MblNkZ8MvwCtP6PlBNGdQcGKKabtuHA==} - engines: {node: '>=14.*'} + /@biomejs/biome@1.8.0: + resolution: {integrity: sha512-34xcE2z8GWrIz1sCFEmlHT/+4d+SN7YOqqvzlAKXKvaWPRJ2/NUwxPbRsP01P9QODkQ5bvGvc9rpBihmP+7RJQ==} + engines: {node: '>=14.21.3'} hasBin: true requiresBuild: true optionalDependencies: - '@biomejs/cli-darwin-arm64': 1.1.2 - '@biomejs/cli-darwin-x64': 1.1.2 - '@biomejs/cli-linux-arm64': 1.1.2 - '@biomejs/cli-linux-x64': 1.1.2 - '@biomejs/cli-win32-arm64': 1.1.2 - '@biomejs/cli-win32-x64': 1.1.2 - dev: true - - /@biomejs/cli-darwin-arm64@1.1.2: - resolution: {integrity: sha512-YyqWeNZchPxlvxtdo2vMBkzrwllaNS3+DZ6j01mUCVIZE9kAzF/edMV2O38L2AEtnRLU1TI1f71Jai3ThILClg==} - engines: {node: '>=14.*'} + '@biomejs/cli-darwin-arm64': 1.8.0 + '@biomejs/cli-darwin-x64': 1.8.0 + '@biomejs/cli-linux-arm64': 1.8.0 + '@biomejs/cli-linux-arm64-musl': 1.8.0 + '@biomejs/cli-linux-x64': 1.8.0 + '@biomejs/cli-linux-x64-musl': 1.8.0 + '@biomejs/cli-win32-arm64': 1.8.0 + '@biomejs/cli-win32-x64': 1.8.0 + dev: true + + /@biomejs/cli-darwin-arm64@1.8.0: + resolution: {integrity: sha512-dBAYzfIJ1JmWigKlWourT3sJ3I60LZPjqNwwlsyFjiv5AV7vPeWlHVVIImV2BpINwNjZQhpXnwDfVnGS4vr7AA==} + engines: {node: '>=14.21.3'} cpu: [arm64] os: [darwin] requiresBuild: true dev: true optional: true - /@biomejs/cli-darwin-x64@1.1.2: - resolution: {integrity: sha512-Sofxcu50AHJyQS6Xx3OF2egQQ7Un5YFVF5/umNFa+kSNrrCu/ucmzrk8FcGS2dOSs4L2LqD6ZDWjvbcikjzLYQ==} - engines: {node: '>=14.*'} + /@biomejs/cli-darwin-x64@1.8.0: + resolution: {integrity: sha512-ZTTSD0bP0nn9UpRDGQrQNTILcYSj+IkxTYr3CAV64DWBDtQBomlk2oVKWzDaA1LOhpAsTh0giLCbPJaVk2jfMQ==} + engines: {node: '>=14.21.3'} cpu: [x64] os: [darwin] requiresBuild: true dev: true optional: true - /@biomejs/cli-linux-arm64@1.1.2: - resolution: {integrity: sha512-wtaQgpoVMZEKf1GlDlFGAJP1j6gnh4L4kJN8PQPOBAdKIUZ/YSjqVp0z28vli5xCQ57xCn1gH4Xoqw2gVYu1tQ==} - engines: {node: '>=14.*'} + /@biomejs/cli-linux-arm64-musl@1.8.0: + resolution: {integrity: sha512-+ee/pZWsvhDv6eRI00krRNSgAg8DKSxzOv3LUsCjto6N1VzqatTASeQv2HRfG1nitf79rRKM75LkMJbqEfiKww==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@biomejs/cli-linux-arm64@1.8.0: + resolution: {integrity: sha512-cx725jTlJS6dskvJJwwCQaaMRBKE2Qss7ukzmx27Rn/DXRxz6tnnBix4FUGPf1uZfwrERkiJlbWM05JWzpvvXg==} + engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] requiresBuild: true dev: true optional: true - /@biomejs/cli-linux-x64@1.1.2: - resolution: {integrity: sha512-TYIUjCXbY+kxnJgv8GESplMagB1GdOcMV21JGRATqnhUI4BvG6sjs3gfi+sdjLBQdbHhsISXW3yfUlv07HKqhg==} - engines: {node: '>=14.*'} + /@biomejs/cli-linux-x64-musl@1.8.0: + resolution: {integrity: sha512-VPA4ocrAOak50VYl8gOAVnjuFFDpIUolShntc/aWM0pZfSIMbRucxnrfUfp44EVwayxjK6ruJTR5xEWj93WvDA==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@biomejs/cli-linux-x64@1.8.0: + resolution: {integrity: sha512-cmgmhlD4QUxMhL1VdaNqnB81xBHb3R7huVNyYnPYzP+AykZ7XqJbPd1KcWAszNjUk2AHdx0aLKEBwCOWemxb2g==} + engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] requiresBuild: true dev: true optional: true - /@biomejs/cli-win32-arm64@1.1.2: - resolution: {integrity: sha512-yApn85KuJ+Ty5zxbqWnaifX4ONtZG+snu12RNKi8fxSVVCXzQ/k2PfsWQbsyvCG05qshSvNKtM54cuf+vhUIsw==} - engines: {node: '>=14.*'} + /@biomejs/cli-win32-arm64@1.8.0: + resolution: {integrity: sha512-J31spvlh39FfRHQacYXxJX9PvTCH/a8+2Jx9D1lxw+LSF0JybqZcw/4JrlFUWUl4kF3yv8AuYUK0sENScc3g9w==} + engines: {node: '>=14.21.3'} cpu: [arm64] os: [win32] requiresBuild: true dev: true optional: true - /@biomejs/cli-win32-x64@1.1.2: - resolution: {integrity: sha512-qebNvIrFj2TJ+K0JVGo1HkgV2y5jis6aOZDC1SWuk53GnqjSLdR+p1v86ZByOjYr1v+tjc67EXmEepk06VVvpA==} - engines: {node: '>=14.*'} + /@biomejs/cli-win32-x64@1.8.0: + resolution: {integrity: sha512-uPHHvu76JC1zYe9zZDcOU9PAg+1MZmPuNgWkb5jljaDeATvzLFPB+0nuJTilf603LXL+E8IdPQAO61Wy2VuEJA==} + engines: {node: '>=14.21.3'} cpu: [x64] os: [win32] requiresBuild: true