From 7f66419036bcca977895b0b9e99e60366bb7101b Mon Sep 17 00:00:00 2001 From: Lewis Date: Wed, 18 Mar 2026 18:36:16 +0200 Subject: [PATCH] refactor(frontend): delete type system boilerplate --- frontend/src/lib/api-validated.ts | 440 --------------------------- frontend/src/lib/types/api.ts | 39 --- frontend/src/lib/types/branded.ts | 131 -------- frontend/src/lib/types/exhaustive.ts | 49 --- frontend/src/lib/types/index.ts | 5 - frontend/src/lib/types/result.ts | 85 ------ frontend/src/lib/types/schemas.ts | 329 -------------------- frontend/src/lib/types/totp-state.ts | 12 - 8 files changed, 1090 deletions(-) delete mode 100644 frontend/src/lib/api-validated.ts delete mode 100644 frontend/src/lib/types/exhaustive.ts delete mode 100644 frontend/src/lib/types/index.ts delete mode 100644 frontend/src/lib/types/schemas.ts diff --git a/frontend/src/lib/api-validated.ts b/frontend/src/lib/api-validated.ts deleted file mode 100644 index a2d797b..0000000 --- a/frontend/src/lib/api-validated.ts +++ /dev/null @@ -1,440 +0,0 @@ -import { z } from "zod"; -import { err, ok, type Result } from "./types/result.ts"; -import { ApiError } from "./api.ts"; -import type { - AccessToken, - Did, - Nsid, - RefreshToken, - Rkey, -} from "./types/branded.ts"; -import { - accountInfoSchema, - appPasswordSchema, - createdAppPasswordSchema, - createRecordResponseSchema, - didDocumentSchema, - enableTotpResponseSchema, - legacyLoginPreferenceSchema, - listPasskeysResponseSchema, - listRecordsResponseSchema, - listSessionsResponseSchema, - listTrustedDevicesResponseSchema, - notificationPrefsSchema, - passwordStatusSchema, - reauthStatusSchema, - recordResponseSchema, - repoDescriptionSchema, - searchAccountsResponseSchema, - serverConfigSchema, - serverDescriptionSchema, - serverStatsSchema, - sessionSchema, - successResponseSchema, - totpSecretSchema, - totpStatusSchema, - type ValidatedAccountInfo, - type ValidatedAppPassword, - type ValidatedCreatedAppPassword, - type ValidatedCreateRecordResponse, - type ValidatedDidDocument, - type ValidatedEnableTotpResponse, - type ValidatedLegacyLoginPreference, - type ValidatedListPasskeysResponse, - type ValidatedListRecordsResponse, - type ValidatedListSessionsResponse, - type ValidatedListTrustedDevicesResponse, - type ValidatedNotificationPrefs, - type ValidatedPasswordStatus, - type ValidatedReauthStatus, - type ValidatedRecordResponse, - type ValidatedRepoDescription, - type ValidatedSearchAccountsResponse, - type ValidatedServerConfig, - type ValidatedServerDescription, - type ValidatedServerStats, - type ValidatedSession, - type ValidatedSuccessResponse, - type ValidatedTotpSecret, - type ValidatedTotpStatus, -} from "./types/schemas.ts"; - -const API_BASE = "/xrpc"; - -interface XrpcOptions { - method?: "GET" | "POST"; - params?: Record; - body?: unknown; - token?: string; -} - -class ValidationError extends Error { - constructor( - public issues: z.ZodIssue[], - message: string = "API response validation failed", - ) { - super(message); - this.name = "ValidationError"; - } -} - -async function xrpcValidated( - method: string, - schema: z.ZodType, - options?: XrpcOptions, -): Promise> { - const { method: httpMethod = "GET", params, body, token } = options ?? {}; - let url = `${API_BASE}/${method}`; - if (params) { - const searchParams = new URLSearchParams(params); - url += `?${searchParams}`; - } - const headers: Record = {}; - if (token) { - headers["Authorization"] = `Bearer ${token}`; - } - if (body) { - headers["Content-Type"] = "application/json"; - } - - try { - const res = await fetch(url, { - method: httpMethod, - headers, - body: body ? JSON.stringify(body) : undefined, - }); - - if (!res.ok) { - const errData = await res.json().catch(() => ({ - error: "Unknown", - message: res.statusText, - })); - return err(new ApiError(res.status, errData.error, errData.message)); - } - - const data = await res.json(); - const parsed = schema.safeParse(data); - - if (!parsed.success) { - return err(new ValidationError(parsed.error.issues)); - } - - return ok(parsed.data); - } catch (e) { - if (e instanceof ApiError || e instanceof ValidationError) { - return err(e); - } - return err( - new ApiError(0, "Unknown", e instanceof Error ? e.message : String(e)), - ); - } -} - -export const validatedApi = { - getSession( - token: AccessToken, - ): Promise> { - return xrpcValidated("com.atproto.server.getSession", sessionSchema, { - token, - }); - }, - - refreshSession( - refreshJwt: RefreshToken, - ): Promise> { - return xrpcValidated("com.atproto.server.refreshSession", sessionSchema, { - method: "POST", - token: refreshJwt, - }); - }, - - createSession( - identifier: string, - password: string, - ): Promise> { - return xrpcValidated("com.atproto.server.createSession", sessionSchema, { - method: "POST", - body: { identifier, password }, - }); - }, - - describeServer(): Promise< - Result - > { - return xrpcValidated( - "com.atproto.server.describeServer", - serverDescriptionSchema, - ); - }, - - listAppPasswords( - token: AccessToken, - ): Promise< - Result<{ passwords: ValidatedAppPassword[] }, ApiError | ValidationError> - > { - return xrpcValidated( - "com.atproto.server.listAppPasswords", - z.object({ passwords: z.array(appPasswordSchema) }), - { token }, - ); - }, - - createAppPassword( - token: AccessToken, - name: string, - scopes?: string, - ): Promise> { - return xrpcValidated( - "com.atproto.server.createAppPassword", - createdAppPasswordSchema, - { - method: "POST", - token, - body: { name, scopes }, - }, - ); - }, - - listSessions( - token: AccessToken, - ): Promise< - Result - > { - return xrpcValidated("_account.listSessions", listSessionsResponseSchema, { - token, - }); - }, - - getTotpStatus( - token: AccessToken, - ): Promise> { - return xrpcValidated("com.atproto.server.getTotpStatus", totpStatusSchema, { - token, - }); - }, - - createTotpSecret( - token: AccessToken, - ): Promise> { - return xrpcValidated( - "com.atproto.server.createTotpSecret", - totpSecretSchema, - { - method: "POST", - token, - }, - ); - }, - - enableTotp( - token: AccessToken, - code: string, - ): Promise> { - return xrpcValidated( - "com.atproto.server.enableTotp", - enableTotpResponseSchema, - { - method: "POST", - token, - body: { code }, - }, - ); - }, - - listPasskeys( - token: AccessToken, - ): Promise< - Result - > { - return xrpcValidated( - "com.atproto.server.listPasskeys", - listPasskeysResponseSchema, - { token }, - ); - }, - - listTrustedDevices( - token: AccessToken, - ): Promise< - Result - > { - return xrpcValidated( - "_account.listTrustedDevices", - listTrustedDevicesResponseSchema, - { token }, - ); - }, - - getReauthStatus( - token: AccessToken, - ): Promise> { - return xrpcValidated("_account.getReauthStatus", reauthStatusSchema, { - token, - }); - }, - - getNotificationPrefs( - token: AccessToken, - ): Promise> { - return xrpcValidated( - "_account.getNotificationPrefs", - notificationPrefsSchema, - { token }, - ); - }, - - getDidDocument( - token: AccessToken, - ): Promise> { - return xrpcValidated("_account.getDidDocument", didDocumentSchema, { - token, - }); - }, - - describeRepo( - token: AccessToken, - repo: Did, - ): Promise> { - return xrpcValidated( - "com.atproto.repo.describeRepo", - repoDescriptionSchema, - { - token, - params: { repo }, - }, - ); - }, - - listRecords( - token: AccessToken, - repo: Did, - collection: Nsid, - options?: { limit?: number; cursor?: string; reverse?: boolean }, - ): Promise> { - const params: Record = { repo, collection }; - if (options?.limit) params.limit = String(options.limit); - if (options?.cursor) params.cursor = options.cursor; - if (options?.reverse) params.reverse = "true"; - return xrpcValidated( - "com.atproto.repo.listRecords", - listRecordsResponseSchema, - { - token, - params, - }, - ); - }, - - getRecord( - token: AccessToken, - repo: Did, - collection: Nsid, - rkey: Rkey, - ): Promise> { - return xrpcValidated("com.atproto.repo.getRecord", recordResponseSchema, { - token, - params: { repo, collection, rkey }, - }); - }, - - createRecord( - token: AccessToken, - repo: Did, - collection: Nsid, - record: unknown, - rkey?: Rkey, - ): Promise< - Result - > { - return xrpcValidated( - "com.atproto.repo.createRecord", - createRecordResponseSchema, - { - method: "POST", - token, - body: { repo, collection, record, rkey }, - }, - ); - }, - - getServerStats( - token: AccessToken, - ): Promise> { - return xrpcValidated("_admin.getServerStats", serverStatsSchema, { token }); - }, - - getServerConfig(): Promise< - Result - > { - return xrpcValidated("_server.getConfig", serverConfigSchema); - }, - - getPasswordStatus( - token: AccessToken, - ): Promise> { - return xrpcValidated("_account.getPasswordStatus", passwordStatusSchema, { - token, - }); - }, - - changePassword( - token: AccessToken, - currentPassword: string, - newPassword: string, - ): Promise> { - return xrpcValidated("_account.changePassword", successResponseSchema, { - method: "POST", - token, - body: { currentPassword, newPassword }, - }); - }, - - getLegacyLoginPreference( - token: AccessToken, - ): Promise< - Result - > { - return xrpcValidated( - "_account.getLegacyLoginPreference", - legacyLoginPreferenceSchema, - { token }, - ); - }, - - getAccountInfo( - token: AccessToken, - did: Did, - ): Promise> { - return xrpcValidated( - "com.atproto.admin.getAccountInfo", - accountInfoSchema, - { - token, - params: { did }, - }, - ); - }, - - searchAccounts( - token: AccessToken, - options?: { handle?: string; cursor?: string; limit?: number }, - ): Promise< - Result - > { - const params: Record = {}; - if (options?.handle) params.handle = options.handle; - if (options?.cursor) params.cursor = options.cursor; - if (options?.limit) params.limit = String(options.limit); - return xrpcValidated( - "com.atproto.admin.searchAccounts", - searchAccountsResponseSchema, - { - token, - params, - }, - ); - }, - -}; - -export { ValidationError }; diff --git a/frontend/src/lib/types/api.ts b/frontend/src/lib/types/api.ts index 60e3b0b..6fc499e 100644 --- a/frontend/src/lib/types/api.ts +++ b/frontend/src/lib/types/api.ts @@ -88,13 +88,6 @@ type SessionBase = { export type Session = SessionBase & ContactState & AccountState; -export function hasEmail( - session: Session, -): session is Session & { email: EmailAddress } { - return session.contactKind === "email" || - (session.contactKind === "channel" && session.email !== undefined); -} - export function getSessionEmail(session: Session): EmailAddress | undefined { return session.contactKind === "email" ? session.email @@ -103,24 +96,6 @@ export function getSessionEmail(session: Session): EmailAddress | undefined { : undefined; } -export function isEmailVerified(session: Session): boolean { - return session.contactKind === "email" - ? session.emailConfirmed - : session.contactKind === "channel" - ? session.preferredChannelVerified - : false; -} - -export function isMigrated( - session: Session, -): session is Session & { accountKind: "migrated" } { - return session.accountKind === "migrated"; -} - -export function isDeactivated(session: Session): boolean { - return session.accountKind === "deactivated"; -} - export function isActive(session: Session): boolean { return session.accountKind === "active"; } @@ -208,17 +183,6 @@ export interface ConfirmSignupResult { preferredChannelVerified?: boolean; } -export interface ListAppPasswordsResponse { - passwords: AppPassword[]; -} - -export interface AccountInviteCodesResponse { - codes: InviteCodeInfo[]; -} - -export interface CreateInviteCodeResponse { - code: InviteCodeBrand; -} export interface ServerLinks { privacyPolicy?: string; @@ -317,9 +281,6 @@ export interface ListSessionsResponse { sessions: SessionInfo[]; } -export interface RevokeAllSessionsResponse { - revokedCount: number; -} export interface AccountSearchResult { did: Did; diff --git a/frontend/src/lib/types/branded.ts b/frontend/src/lib/types/branded.ts index 7d00fee..64a9860 100644 --- a/frontend/src/lib/types/branded.ts +++ b/frontend/src/lib/types/branded.ts @@ -25,99 +25,6 @@ export type PublicKeyMultibase = Brand; export type DidKeyString = Brand; export type ScopeSet = Brand; -const DID_PLC_REGEX = /^did:plc:[a-z2-7]{24}$/; -const DID_WEB_REGEX = /^did:web:.+$/; -const HANDLE_REGEX = - /^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$/; -const AT_URI_REGEX = /^at:\/\/[^/]+\/[^/]+\/[^/]+$/; -const CID_REGEX = /^[a-z2-7]{59}$|^baf[a-z2-7]+$/; -const NSID_REGEX = - /^[a-z]([a-z0-9-]*[a-z0-9])?(\.[a-z]([a-z0-9-]*[a-z0-9])?)+$/; -const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; -const ISO_DATE_REGEX = - /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$/; - -export function isDid(s: string): s is Did { - return s.startsWith("did:plc:") || s.startsWith("did:web:"); -} - -export function isDidPlc(s: string): s is DidPlc { - return DID_PLC_REGEX.test(s); -} - -export function isDidWeb(s: string): s is DidWeb { - return DID_WEB_REGEX.test(s); -} - -export function isHandle(s: string): s is Handle { - return HANDLE_REGEX.test(s) && s.length <= 253; -} - -export function isAtUri(s: string): s is AtUri { - return AT_URI_REGEX.test(s); -} - -export function isCid(s: string): s is Cid { - return CID_REGEX.test(s); -} - -export function isNsid(s: string): s is Nsid { - return NSID_REGEX.test(s); -} - -export function isEmail(s: string): s is EmailAddress { - return EMAIL_REGEX.test(s); -} - -export function isISODate(s: string): s is ISODateString { - return ISO_DATE_REGEX.test(s); -} - -export function asDid(s: string): Did { - if (!isDid(s)) throw new TypeError(`Invalid DID: ${s}`); - return s; -} - -export function asDidPlc(s: string): DidPlc { - if (!isDidPlc(s)) throw new TypeError(`Invalid DID:PLC: ${s}`); - return s as DidPlc; -} - -export function asDidWeb(s: string): DidWeb { - if (!isDidWeb(s)) throw new TypeError(`Invalid DID:WEB: ${s}`); - return s as DidWeb; -} - -export function asHandle(s: string): Handle { - if (!isHandle(s)) throw new TypeError(`Invalid handle: ${s}`); - return s; -} - -export function asAtUri(s: string): AtUri { - if (!isAtUri(s)) throw new TypeError(`Invalid AT-URI: ${s}`); - return s; -} - -export function asCid(s: string): Cid { - if (!isCid(s)) throw new TypeError(`Invalid CID: ${s}`); - return s; -} - -export function asNsid(s: string): Nsid { - if (!isNsid(s)) throw new TypeError(`Invalid NSID: ${s}`); - return s; -} - -export function asEmail(s: string): EmailAddress { - if (!isEmail(s)) throw new TypeError(`Invalid email: ${s}`); - return s; -} - -export function asISODate(s: string): ISODateString { - if (!isISODate(s)) throw new TypeError(`Invalid ISO date: ${s}`); - return s; -} - export function unsafeAsDid(s: string): Did { return s as Did; } @@ -134,26 +41,10 @@ export function unsafeAsRefreshToken(s: string): RefreshToken { return s as RefreshToken; } -export function unsafeAsServiceToken(s: string): ServiceToken { - return s as ServiceToken; -} - -export function unsafeAsSetupToken(s: string): SetupToken { - return s as SetupToken; -} - -export function unsafeAsCid(s: string): Cid { - return s as Cid; -} - export function unsafeAsRkey(s: string): Rkey { return s as Rkey; } -export function unsafeAsAtUri(s: string): AtUri { - return s as AtUri; -} - export function unsafeAsNsid(s: string): Nsid { return s as Nsid; } @@ -172,29 +63,7 @@ export function unsafeAsInviteCode(s: string): InviteCode { return s as InviteCode; } -export function unsafeAsPublicKeyMultibase(s: string): PublicKeyMultibase { - return s as PublicKeyMultibase; -} - -export function unsafeAsDidKey(s: string): DidKeyString { - return s as DidKeyString; -} - export function unsafeAsScopeSet(s: string): ScopeSet { return s as ScopeSet; } -export function parseAtUri( - uri: AtUri, -): { repo: Did; collection: Nsid; rkey: Rkey } { - const parts = uri.replace("at://", "").split("/"); - return { - repo: unsafeAsDid(parts[0]), - collection: unsafeAsNsid(parts[1]), - rkey: unsafeAsRkey(parts[2]), - }; -} - -export function makeAtUri(repo: Did, collection: Nsid, rkey: Rkey): AtUri { - return `at://${repo}/${collection}/${rkey}` as AtUri; -} diff --git a/frontend/src/lib/types/exhaustive.ts b/frontend/src/lib/types/exhaustive.ts deleted file mode 100644 index 7e10a71..0000000 --- a/frontend/src/lib/types/exhaustive.ts +++ /dev/null @@ -1,49 +0,0 @@ -export function assertNever(x: never, message?: string): never { - throw new Error(message ?? `Unexpected value: ${JSON.stringify(x)}`); -} - -export function exhaustive( - value: T, - handlers: Record void>, -): void { - const handler = handlers[value]; - if (handler) { - handler(); - } else { - assertNever(value as never, `Unhandled case: ${String(value)}`); - } -} - -export function exhaustiveMap( - value: T, - handlers: Record R>, -): R { - const handler = handlers[value]; - if (handler) { - return handler(); - } - return assertNever(value as never, `Unhandled case: ${String(value)}`); -} - -export async function exhaustiveAsync( - value: T, - handlers: Record Promise>, -): Promise { - const handler = handlers[value]; - if (handler) { - await handler(); - } else { - assertNever(value as never, `Unhandled case: ${String(value)}`); - } -} - -export async function exhaustiveMapAsync( - value: T, - handlers: Record Promise>, -): Promise { - const handler = handlers[value]; - if (handler) { - return handler(); - } - return assertNever(value as never, `Unhandled case: ${String(value)}`); -} diff --git a/frontend/src/lib/types/index.ts b/frontend/src/lib/types/index.ts deleted file mode 100644 index 7791987..0000000 --- a/frontend/src/lib/types/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from "./result.ts"; -export * from "./branded.ts"; -export * from "./exhaustive.ts"; -export * from "./api.ts"; -export * from "./routes.ts"; diff --git a/frontend/src/lib/types/result.ts b/frontend/src/lib/types/result.ts index a4cde54..e760950 100644 --- a/frontend/src/lib/types/result.ts +++ b/frontend/src/lib/types/result.ts @@ -22,90 +22,5 @@ export function isErr( return !result.ok; } -export function map( - result: Result, - fn: (t: T) => U, -): Result { - return result.ok ? ok(fn(result.value)) : result; -} -export function mapErr( - result: Result, - fn: (e: E) => F, -): Result { - return result.ok ? result : err(fn(result.error)); -} -export function flatMap( - result: Result, - fn: (t: T) => Result, -): Result { - return result.ok ? fn(result.value) : result; -} - -export function unwrap(result: Result): T { - if (result.ok) return result.value; - throw result.error instanceof Error - ? result.error - : new Error(String(result.error)); -} - -export function unwrapOr(result: Result, defaultValue: T): T { - return result.ok ? result.value : defaultValue; -} - -export function unwrapOrElse(result: Result, fn: (e: E) => T): T { - return result.ok ? result.value : fn(result.error); -} - -export function match( - result: Result, - handlers: { ok: (t: T) => U; err: (e: E) => U }, -): U { - return result.ok ? handlers.ok(result.value) : handlers.err(result.error); -} - -export async function tryAsync( - fn: () => Promise, -): Promise> { - try { - return ok(await fn()); - } catch (e) { - return err(e instanceof Error ? e : new Error(String(e))); - } -} - -export async function tryAsyncWith( - fn: () => Promise, - mapError: (e: unknown) => E, -): Promise> { - try { - return ok(await fn()); - } catch (e) { - return err(mapError(e)); - } -} - -export function fromNullable(value: T | null | undefined): Result { - return value != null ? ok(value) : err(null); -} - -export function toNullable(result: Result): T | null { - return result.ok ? result.value : null; -} - -export function collect(results: Result[]): Result { - const values: T[] = []; - for (const result of results) { - if (!result.ok) return result; - values.push(result.value); - } - return ok(values); -} - -export async function collectAsync( - results: Promise>[], -): Promise> { - const settled = await Promise.all(results); - return collect(settled); -} diff --git a/frontend/src/lib/types/schemas.ts b/frontend/src/lib/types/schemas.ts deleted file mode 100644 index 9b13c73..0000000 --- a/frontend/src/lib/types/schemas.ts +++ /dev/null @@ -1,329 +0,0 @@ -import { z } from "zod"; -import { - unsafeAsAccessToken, - unsafeAsAtUri, - unsafeAsCid, - unsafeAsDid, - unsafeAsEmail, - unsafeAsHandle, - unsafeAsInviteCode, - unsafeAsISODate, - unsafeAsNsid, - unsafeAsPublicKeyMultibase, - unsafeAsRefreshToken, - unsafeAsRkey, -} from "./branded.ts"; - -const did = z.string().transform((s) => unsafeAsDid(s)); -const handle = z.string().transform((s) => unsafeAsHandle(s)); -const accessToken = z.string().transform((s) => unsafeAsAccessToken(s)); -const refreshToken = z.string().transform((s) => unsafeAsRefreshToken(s)); -const cid = z.string().transform((s) => unsafeAsCid(s)); -const nsid = z.string().transform((s) => unsafeAsNsid(s)); -const atUri = z.string().transform((s) => unsafeAsAtUri(s)); -const _rkey = z.string().transform((s) => unsafeAsRkey(s)); -const isoDate = z.string().transform((s) => unsafeAsISODate(s)); -const email = z.string().transform((s) => unsafeAsEmail(s)); -const inviteCode = z.string().transform((s) => unsafeAsInviteCode(s)); -const publicKeyMultibase = z.string().transform((s) => - unsafeAsPublicKeyMultibase(s) -); - -export const verificationChannel = z.enum([ - "email", - "discord", - "telegram", - "signal", -]); -export const didType = z.enum(["plc", "web", "web-external"]); -export const accountStatus = z.enum([ - "active", - "deactivated", - "migrated", - "suspended", - "deleted", -]); -export const sessionType = z.enum(["oauth", "legacy", "app_password"]); -export const reauthMethod = z.enum(["password", "totp", "passkey"]); - -export const sessionSchema = z.object({ - did: did, - handle: handle, - email: email.optional(), - emailConfirmed: z.boolean().optional(), - preferredChannel: verificationChannel.optional(), - preferredChannelVerified: z.boolean().optional(), - isAdmin: z.boolean().optional(), - active: z.boolean().optional(), - status: accountStatus.optional(), - migratedToPds: z.string().optional(), - migratedAt: isoDate.optional(), - accessJwt: accessToken, - refreshJwt: refreshToken, -}); - -export const serverLinksSchema = z.object({ - privacyPolicy: z.string().optional(), - termsOfService: z.string().optional(), -}); - -export const serverDescriptionSchema = z.object({ - availableUserDomains: z.array(z.string()), - inviteCodeRequired: z.boolean(), - links: serverLinksSchema.optional(), - version: z.string().optional(), - availableCommsChannels: z.array(verificationChannel).optional(), - selfHostedDidWebEnabled: z.boolean().optional(), -}); - -export const appPasswordSchema = z.object({ - name: z.string(), - createdAt: isoDate, - scopes: z.string().optional(), - createdByController: z.string().optional(), -}); - -export const createdAppPasswordSchema = z.object({ - name: z.string(), - password: z.string(), - createdAt: isoDate, - scopes: z.string().optional(), -}); - -export const inviteCodeUseSchema = z.object({ - usedBy: did, - usedByHandle: handle.optional(), - usedAt: isoDate, -}); - -export const inviteCodeInfoSchema = z.object({ - code: inviteCode, - available: z.number(), - disabled: z.boolean(), - forAccount: did, - createdBy: did, - createdAt: isoDate, - uses: z.array(inviteCodeUseSchema), -}); - -export const sessionInfoSchema = z.object({ - id: z.string(), - sessionType: sessionType, - clientName: z.string().nullable(), - createdAt: isoDate, - expiresAt: isoDate, - isCurrent: z.boolean(), -}); - -export const listSessionsResponseSchema = z.object({ - sessions: z.array(sessionInfoSchema), -}); - -export const totpStatusSchema = z.object({ - enabled: z.boolean(), - hasBackupCodes: z.boolean(), -}); - -export const totpSecretSchema = z.object({ - uri: z.string(), - qrBase64: z.string(), -}); - -export const enableTotpResponseSchema = z.object({ - success: z.boolean(), - backupCodes: z.array(z.string()), -}); - -export const passkeyInfoSchema = z.object({ - id: z.string(), - credentialId: z.string(), - friendlyName: z.string().nullable(), - createdAt: isoDate, - lastUsed: isoDate.nullable(), -}); - -export const listPasskeysResponseSchema = z.object({ - passkeys: z.array(passkeyInfoSchema), -}); - -export const trustedDeviceSchema = z.object({ - id: z.string(), - userAgent: z.string().nullable(), - friendlyName: z.string().nullable(), - trustedAt: isoDate.nullable(), - trustedUntil: isoDate.nullable(), - lastSeenAt: isoDate, -}); - -export const listTrustedDevicesResponseSchema = z.object({ - devices: z.array(trustedDeviceSchema), -}); - -export const reauthStatusSchema = z.object({ - requiresReauth: z.boolean(), - lastReauthAt: isoDate.nullable(), - availableMethods: z.array(reauthMethod), -}); - -export const reauthResponseSchema = z.object({ - success: z.boolean(), - reauthAt: isoDate, -}); - -export const notificationPrefsSchema = z.object({ - preferredChannel: verificationChannel, - email: email, - discordUsername: z.string().nullable(), - discordVerified: z.boolean(), - telegramUsername: z.string().nullable(), - telegramVerified: z.boolean(), - signalUsername: z.string().nullable(), - signalVerified: z.boolean(), -}); - -export const verificationMethodSchema = z.object({ - id: z.string(), - type: z.string(), - controller: z.string(), - publicKeyMultibase: publicKeyMultibase, -}); - -export const serviceEndpointSchema = z.object({ - id: z.string(), - type: z.string(), - serviceEndpoint: z.string(), -}); - -export const didDocumentSchema = z.object({ - "@context": z.array(z.string()), - id: did, - alsoKnownAs: z.array(z.string()), - verificationMethod: z.array(verificationMethodSchema), - service: z.array(serviceEndpointSchema), -}); - -export const repoDescriptionSchema = z.object({ - handle: handle, - did: did, - didDoc: didDocumentSchema, - collections: z.array(nsid), - handleIsCorrect: z.boolean(), -}); - -export const recordInfoSchema = z.object({ - uri: atUri, - cid: cid, - value: z.unknown(), -}); - -export const listRecordsResponseSchema = z.object({ - records: z.array(recordInfoSchema), - cursor: z.string().optional(), -}); - -export const recordResponseSchema = z.object({ - uri: atUri, - cid: cid, - value: z.unknown(), -}); - -export const createRecordResponseSchema = z.object({ - uri: atUri, - cid: cid, -}); - -export const serverStatsSchema = z.object({ - userCount: z.number(), - repoCount: z.number(), - recordCount: z.number(), - blobStorageBytes: z.number(), -}); - -export const serverConfigSchema = z.object({ - serverName: z.string(), - primaryColor: z.string().nullable(), - primaryColorDark: z.string().nullable(), - secondaryColor: z.string().nullable(), - secondaryColorDark: z.string().nullable(), - logoCid: cid.nullable(), -}); - -export const passwordStatusSchema = z.object({ - hasPassword: z.boolean(), -}); - -export const successResponseSchema = z.object({ - success: z.boolean(), -}); - -export const legacyLoginPreferenceSchema = z.object({ - allowLegacyLogin: z.boolean(), - hasMfa: z.boolean(), -}); - -export const accountInfoSchema = z.object({ - did: did, - handle: handle, - email: email.optional(), - indexedAt: isoDate, - emailConfirmedAt: isoDate.optional(), - invitesDisabled: z.boolean().optional(), - deactivatedAt: isoDate.optional(), -}); - -export const searchAccountsResponseSchema = z.object({ - cursor: z.string().optional(), - accounts: z.array(accountInfoSchema), -}); - -export type ValidatedSession = z.infer; -export type ValidatedServerDescription = z.infer< - typeof serverDescriptionSchema ->; -export type ValidatedAppPassword = z.infer; -export type ValidatedCreatedAppPassword = z.infer< - typeof createdAppPasswordSchema ->; -export type ValidatedInviteCodeInfo = z.infer; -export type ValidatedSessionInfo = z.infer; -export type ValidatedListSessionsResponse = z.infer< - typeof listSessionsResponseSchema ->; -export type ValidatedTotpStatus = z.infer; -export type ValidatedTotpSecret = z.infer; -export type ValidatedEnableTotpResponse = z.infer< - typeof enableTotpResponseSchema ->; -export type ValidatedPasskeyInfo = z.infer; -export type ValidatedListPasskeysResponse = z.infer< - typeof listPasskeysResponseSchema ->; -export type ValidatedTrustedDevice = z.infer; -export type ValidatedListTrustedDevicesResponse = z.infer< - typeof listTrustedDevicesResponseSchema ->; -export type ValidatedReauthStatus = z.infer; -export type ValidatedReauthResponse = z.infer; -export type ValidatedNotificationPrefs = z.infer< - typeof notificationPrefsSchema ->; -export type ValidatedDidDocument = z.infer; -export type ValidatedRepoDescription = z.infer; -export type ValidatedListRecordsResponse = z.infer< - typeof listRecordsResponseSchema ->; -export type ValidatedRecordResponse = z.infer; -export type ValidatedCreateRecordResponse = z.infer< - typeof createRecordResponseSchema ->; -export type ValidatedServerStats = z.infer; -export type ValidatedServerConfig = z.infer; -export type ValidatedPasswordStatus = z.infer; -export type ValidatedSuccessResponse = z.infer; -export type ValidatedLegacyLoginPreference = z.infer< - typeof legacyLoginPreferenceSchema ->; -export type ValidatedAccountInfo = z.infer; -export type ValidatedSearchAccountsResponse = z.infer< - typeof searchAccountsResponseSchema ->; diff --git a/frontend/src/lib/types/totp-state.ts b/frontend/src/lib/types/totp-state.ts index 3c54283..b321e8b 100644 --- a/frontend/src/lib/types/totp-state.ts +++ b/frontend/src/lib/types/totp-state.ts @@ -61,18 +61,6 @@ export function finish(_state: TotpBackup): TotpIdle { return idleState; } -export function isIdle(state: TotpSetupState): state is TotpIdle { - return state.step === "idle"; -} - -export function isQr(state: TotpSetupState): state is TotpQr { - return state.step === "qr"; -} - -export function isVerify(state: TotpSetupState): state is TotpVerify { - return state.step === "verify"; -} - export function isBackup(state: TotpSetupState): state is TotpBackup { return state.step === "backup"; }