From 45d74bc024d3a20b1b6da7d4caeb79ee8f993710 Mon Sep 17 00:00:00 2001 From: Shibo Lyu Date: Thu, 22 May 2025 02:11:42 +0800 Subject: [PATCH] refactor: migrate to zod v4 --- src/crypto/publicKey.ts | 10 +++++----- src/crypto/signAndVerify.test.ts | 2 +- src/crypto/signedPayload.test.ts | 2 +- src/crypto/signedPayload.ts | 6 +++--- src/identity/actKey.test.ts | 2 +- src/identity/actKey.ts | 20 +++++++++----------- src/identity/identityDescription.test.ts | 2 +- src/identity/identityDescription.ts | 6 ++---- src/identity/mod.ts | 2 +- src/identity/profile.test.ts | 2 +- src/identity/profile.ts | 10 ++++++---- src/richText/mod.ts | 2 +- src/richText/richText.test.ts | 2 +- src/richText/richText.ts | 2 +- src/richText/span.test.ts | 2 +- src/richText/span.ts | 4 ++-- 16 files changed, 37 insertions(+), 39 deletions(-) diff --git a/src/crypto/publicKey.ts b/src/crypto/publicKey.ts index 1d9fa78..d52075c 100644 --- a/src/crypto/publicKey.ts +++ b/src/crypto/publicKey.ts @@ -1,4 +1,4 @@ -import type z from "zod"; +import type { z } from "zod/v4"; import { type BlahSignedPayload, blahSignedPayloadSchemaOf, @@ -22,9 +22,7 @@ export class BlahPublicKey { this.name = id.slice(0, 4) + "..." + id.slice(-4); } - static async fromPublicKey( - publicKey: CryptoKey, - ): Promise { + static async fromPublicKey(publicKey: CryptoKey): Promise { const rawKey = await crypto.subtle.exportKey("raw", publicKey); const id = bufToHex(rawKey); return new BlahPublicKey(publicKey, id); @@ -67,7 +65,9 @@ export class BlahPublicKey { options: SignOrVerifyOptions = {}, ): Promise<{ payload: z.infer

; key: BlahPublicKey }> { const signedPayloadSchema = blahSignedPayloadSchemaOf(schema); - const parsed = signedPayloadSchema.parse(signedPayload) as z.infer

; + const parsed = signedPayloadSchema.parse( + signedPayload, + ) as BlahSignedPayload>; return await BlahPublicKey.verifyPayload(parsed, options); } diff --git a/src/crypto/signAndVerify.test.ts b/src/crypto/signAndVerify.test.ts index 1831ddd..1d1cb53 100644 --- a/src/crypto/signAndVerify.test.ts +++ b/src/crypto/signAndVerify.test.ts @@ -1,7 +1,7 @@ import { expect, test } from "vitest"; import { BlahKeyPair } from "./keypair.ts"; -import { z } from "zod"; +import { z } from "zod/v4"; import { BlahPublicKey } from "./publicKey.ts"; import type { SignOrVerifyOptions } from "./signAndVerify.ts"; diff --git a/src/crypto/signedPayload.test.ts b/src/crypto/signedPayload.test.ts index 0b49c0c..de06395 100644 --- a/src/crypto/signedPayload.test.ts +++ b/src/crypto/signedPayload.test.ts @@ -1,6 +1,6 @@ import { expectTypeOf, test } from "vitest"; -import z from "zod"; +import { z } from "zod/v4"; import type { BlahSignedPayload } from "./mod.ts"; import { type BlahPayloadSignee, diff --git a/src/crypto/signedPayload.ts b/src/crypto/signedPayload.ts index 1fdc43e..b5c25f0 100644 --- a/src/crypto/signedPayload.ts +++ b/src/crypto/signedPayload.ts @@ -1,10 +1,10 @@ -import z from "zod"; +import { z } from "zod/v4"; export function blahPayloadSigneeSchemaOf

(schema: P) { return z.object({ - nonce: z.number().int(), + nonce: z.int(), payload: schema, - timestamp: z.number().int(), + timestamp: z.int(), id_key: z.string(), act_key: z.string(), }); diff --git a/src/identity/actKey.test.ts b/src/identity/actKey.test.ts index 66298bc..4c461c1 100644 --- a/src/identity/actKey.test.ts +++ b/src/identity/actKey.test.ts @@ -1,7 +1,7 @@ import { expectTypeOf, test } from "vitest"; import { type BlahActKeyRecord, blahActKeyRecordSchema } from "./actKey.ts"; -import z from "zod"; +import { z } from "zod/v4"; test("BlahActKeyRecord typed correctly", () => { expectTypeOf< diff --git a/src/identity/actKey.ts b/src/identity/actKey.ts index de02190..cfc91ee 100644 --- a/src/identity/actKey.ts +++ b/src/identity/actKey.ts @@ -1,4 +1,4 @@ -import z from "zod"; +import { z } from "zod/v4"; import { BlahPublicKey } from "../crypto/publicKey.ts"; import { BlahKeyPair } from "../crypto/keypair.ts"; import type { BlahSignedPayload } from "../crypto/signedPayload.ts"; @@ -7,7 +7,7 @@ import type { SignOrVerifyOptions } from "../crypto/signAndVerify.ts"; export const blahActKeyRecordSchema = z.object({ typ: z.literal("user_act_key"), act_key: z.string(), - expire_time: z.number().int(), + expire_time: z.int(), comment: z.string(), }); @@ -61,8 +61,8 @@ export class BlahActKey { sigValid = false; } - const key: BlahPublicKey | BlahKeyPair = keypair ?? - await BlahPublicKey.fromID(record.act_key); + const key: BlahPublicKey | BlahKeyPair = + keypair ?? (await BlahPublicKey.fromID(record.act_key)); const fullConfig: Required = { expiresAt: new Date(record.expire_time * 1000), comment: record.comment, @@ -142,10 +142,10 @@ export class BlahActKey { ): Promise> { if (!this.canSign) throw new Error("Cannot sign without a private key"); - return await (this.internalKey as BlahKeyPair).signPayload( - payload, - { ...options, identityKeyId: this.internalIdKeyPublic.id }, - ); + return await (this.internalKey as BlahKeyPair).signPayload(payload, { + ...options, + identityKeyId: this.internalIdKeyPublic.id, + }); } /** @@ -156,9 +156,7 @@ export class BlahActKey { * * @param payload The signed payload to verify. */ - async verifyPayload

( - payload: BlahSignedPayload

, - ): Promise

{ + async verifyPayload

(payload: BlahSignedPayload

): Promise

{ if (new Date(payload.signee.timestamp * 1000) > this.internalExpiresAt) { throw new Error("Key was expired at the time of signing"); } diff --git a/src/identity/identityDescription.test.ts b/src/identity/identityDescription.test.ts index 36c4154..742a393 100644 --- a/src/identity/identityDescription.test.ts +++ b/src/identity/identityDescription.test.ts @@ -6,7 +6,7 @@ import { getIdentityDescriptionFileURL, identityDescriptionFilePath, } from "./identityDescription.ts"; -import { z } from "zod"; +import { z } from "zod/v4"; test("BlahIdentityDescription typed correctly", () => { expectTypeOf< diff --git a/src/identity/identityDescription.ts b/src/identity/identityDescription.ts index 40840bd..55b45b9 100644 --- a/src/identity/identityDescription.ts +++ b/src/identity/identityDescription.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; import { blahSignedPayloadSchemaOf } from "../crypto/signedPayload.ts"; import { type BlahActKeyRecord, blahActKeyRecordSchema } from "./actKey.ts"; import { @@ -32,9 +32,7 @@ export const identityDescriptionFilePath = "/.well-known/blah/identity.json"; * @returns The full URL to the identity description file. * @throws Error if the ID URL format is invalid. */ -export function getIdentityDescriptionFileURL( - idURL: string, -): string { +export function getIdentityDescriptionFileURL(idURL: string): string { if (!validateIDURLFormat(idURL)) throw new Error("Invalid ID URL format"); const url = new URL(idURL); url.pathname = identityDescriptionFilePath; diff --git a/src/identity/mod.ts b/src/identity/mod.ts index 39270cd..d572480 100644 --- a/src/identity/mod.ts +++ b/src/identity/mod.ts @@ -1,4 +1,4 @@ -import type z from "zod"; +import type { z } from "zod/v4"; export * from "./identity.ts"; diff --git a/src/identity/profile.test.ts b/src/identity/profile.test.ts index 48b9c89..877ce60 100644 --- a/src/identity/profile.test.ts +++ b/src/identity/profile.test.ts @@ -1,6 +1,6 @@ import { expect, test, expectTypeOf } from "vitest"; -import z from "zod"; +import { z } from "zod/v4"; import { type BlahProfile, blahProfileSchema, diff --git a/src/identity/profile.ts b/src/identity/profile.ts index c9ef639..bee6dd9 100644 --- a/src/identity/profile.ts +++ b/src/identity/profile.ts @@ -1,9 +1,9 @@ -import z from "zod"; +import { z } from "zod/v4"; /** Schema for Blah user profile. */ export const blahProfileSchema = z.object({ typ: z.literal("profile"), - preferred_chat_server_urls: z.array(z.string().url()), + preferred_chat_server_urls: z.array(z.url()), id_urls: z.array(z.string().refine(validateIDURLFormat)).min(1), name: z.string(), bio: z.string().optional(), @@ -22,14 +22,16 @@ export type BlahProfile = { export function validateIDURLFormat(url: string): boolean { try { const idURL = new URL(url); - return !!idURL && + return ( + !!idURL && idURL.protocol === "https:" && idURL.pathname === "/" && !url.endsWith("/") && !idURL.search && !idURL.hash && !idURL.username && - !idURL.password; + !idURL.password + ); } catch { return false; } diff --git a/src/richText/mod.ts b/src/richText/mod.ts index 491c7b3..4d01502 100644 --- a/src/richText/mod.ts +++ b/src/richText/mod.ts @@ -6,7 +6,7 @@ * @module */ -import type z from "zod"; +import type z from "zod/v4"; import { type BlahRichTextSpan, diff --git a/src/richText/richText.test.ts b/src/richText/richText.test.ts index 14a027e..73c6d27 100644 --- a/src/richText/richText.test.ts +++ b/src/richText/richText.test.ts @@ -1,7 +1,7 @@ import { expectTypeOf, test } from "vitest"; import { type BlahRichText, blahRichTextSchema } from "./richText.ts"; -import z from "zod"; +import { z } from "zod/v4"; test("BlahRichText typed correctly", () => { expectTypeOf< diff --git a/src/richText/richText.ts b/src/richText/richText.ts index 516ffd3..578e371 100644 --- a/src/richText/richText.ts +++ b/src/richText/richText.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; import { type BlahRichTextSpan, blahRichTextSpanSchema } from "./span.ts"; export const blahRichTextSchema = z.array(blahRichTextSpanSchema); diff --git a/src/richText/span.test.ts b/src/richText/span.test.ts index 6e8f2b0..98f47c1 100644 --- a/src/richText/span.test.ts +++ b/src/richText/span.test.ts @@ -1,7 +1,7 @@ import { expectTypeOf, test } from "vitest"; import { type BlahRichTextSpan, blahRichTextSpanSchema } from "./span.ts"; -import z from "zod"; +import { z } from "zod/v4"; test("BlahRichTextSpan typed correctly", () => { expectTypeOf().toEqualTypeOf< diff --git a/src/richText/span.ts b/src/richText/span.ts index 2ab5052..fc511c9 100644 --- a/src/richText/span.ts +++ b/src/richText/span.ts @@ -1,4 +1,4 @@ -import { z } from "zod"; +import { z } from "zod/v4"; export const blahRichTextSpanAttributesSchema = z.object({ b: z.boolean().default(false), @@ -8,7 +8,7 @@ export const blahRichTextSpanAttributesSchema = z.object({ m: z.boolean().default(false), tag: z.boolean().default(false), spoiler: z.boolean().default(false), - link: z.string().url().optional(), + link: z.url().optional(), }); export type BlahRichTextSpanAttributes = {