🌿 Collaborative wiki on ATProto lichen.wiki
atproto
14
fork

Configure Feed

Select the types of activity you want to include in your feed.

Move to atcute identity resolver

juprodh 70969b9d 67bf50e9

+78 -24
+12 -1
bun.lock
··· 6 6 "name": "atwiki", 7 7 "dependencies": { 8 8 "@atcute/cid": "^2.4.1", 9 + "@atcute/identity-resolver": "^1.2.2", 10 + "@atcute/identity-resolver-node": "^1.0.3", 9 11 "@atcute/jetstream": "^1.1.2", 12 + "@atcute/lexicons": "^1.3.0", 10 13 "@atcute/tid": "^1.1.2", 11 14 "@atproto/api": "^0.13.0", 12 - "@atproto/identity": "^0.4.12", 13 15 "@atproto/jwk-jose": "^0.1.0", 14 16 "@atproto/oauth-client-node": "^0.1.0", 15 17 "@codemirror/lang-markdown": "^6.5.0", ··· 26 28 }, 27 29 "devDependencies": { 28 30 "@atproto/dev-env": "0.3.213", 31 + "@atproto/identity": "^0.4.12", 29 32 "@atproto/sync": "^0.1.40", 30 33 "@biomejs/biome": "^2.4.4", 31 34 "@tailwindcss/cli": "^4.2.1", ··· 56 59 "packages": { 57 60 "@atcute/cid": ["@atcute/cid@2.4.1", "", { "dependencies": { "@atcute/multibase": "^1.1.8", "@atcute/uint8array": "^1.1.1" } }, "sha512-bwhna69RCv7yetXudtj+2qrMPYvhhIQqvJz6YUpUS98v7OdF3X2dnye9Nig2NDrklZcuyOsu7sQo7GOykJXRLQ=="], 58 61 62 + "@atcute/identity": ["@atcute/identity@1.1.4", "", { "dependencies": { "@atcute/lexicons": "^1.2.9", "@badrap/valita": "^0.4.6" } }, "sha512-RCw1IqflfuSYCxK5m0lZCm0UnvIzcUnuhngiBhJEJb9a9Mc2SEf1xP3H8N5r8pvEH1LoAYd6/zrvCNU+uy9esw=="], 63 + 64 + "@atcute/identity-resolver": ["@atcute/identity-resolver@1.2.2", "", { "dependencies": { "@atcute/lexicons": "^1.2.6", "@atcute/util-fetch": "^1.0.5", "@badrap/valita": "^0.4.6" }, "peerDependencies": { "@atcute/identity": "^1.0.0" } }, "sha512-eUh/UH4bFvuXS0X7epYCeJC/kj4rbBXfSRumLEH4smMVwNOgTo7cL/0Srty+P/qVPoZEyXdfEbS0PHJyzoXmHw=="], 65 + 66 + "@atcute/identity-resolver-node": ["@atcute/identity-resolver-node@1.0.3", "", { "dependencies": { "@atcute/lexicons": "^1.2.2" }, "peerDependencies": { "@atcute/identity": "^1.0.0", "@atcute/identity-resolver": "^1.0.0" } }, "sha512-RPH5M4ZRayKRcGnJWUOPVhN5WSYURXXZxKzgVT9lj/WZCH6ij2Vg3P3Eva7GGs0SG1ytnX1XVBTMoIk8nF/SLQ=="], 67 + 59 68 "@atcute/jetstream": ["@atcute/jetstream@1.1.2", "", { "dependencies": { "@atcute/lexicons": "^1.2.2", "@badrap/valita": "^0.4.6", "@mary-ext/event-iterator": "^1.0.0", "@mary-ext/simple-event-emitter": "^1.0.0", "partysocket": "^1.1.5", "type-fest": "^4.41.0", "yocto-queue": "^1.2.1" } }, "sha512-u6p/h2xppp7LE6W/9xErAJ6frfN60s8adZuCKtfAaaBBiiYbb1CfpzN8Uc+2qtJZNorqGvuuDb5572Jmh7yHBQ=="], 60 69 61 70 "@atcute/lexicons": ["@atcute/lexicons@1.3.0", "", { "dependencies": { "@atcute/uint8array": "^1.1.1", "@atcute/util-text": "^1.2.0", "@standard-schema/spec": "^1.1.0", "esm-env": "^1.2.2" } }, "sha512-Eq5y+9onnCXNVUlNiMf31beSXHKqptB7lUo/68YbhlmxdaR7ooywHmahya9goP5AsmlYEA1z+dRPXIDAa9O7cg=="], ··· 67 76 "@atcute/time-ms": ["@atcute/time-ms@1.3.2", "", {}, "sha512-F+qOyR9pO55g1d/QmN+Gr+fimoUQQLusdGSB6pjV0wW5KPILR4oQ4e2ZhWzqUbeHLAgWvgoTTMsMDdz62Xa2tg=="], 68 77 69 78 "@atcute/uint8array": ["@atcute/uint8array@1.1.1", "", {}, "sha512-3LsC8XB8TKe9q/5hOA5sFuzGaIFdJZJNewC5OKa3o/eU6+K7JR6see9Zy2JbQERNVnRl11EzbNov1efgLMAs4g=="], 79 + 80 + "@atcute/util-fetch": ["@atcute/util-fetch@1.0.5", "", { "dependencies": { "@badrap/valita": "^0.4.6" } }, "sha512-qjHj01BGxjSjIFdPiAjSARnodJIIyKxnCMMEcXMESo9TAyND6XZQqrie5fia+LlYWVXdpsTds8uFQwc9jdKTig=="], 70 81 71 82 "@atcute/util-text": ["@atcute/util-text@1.3.1", "", { "dependencies": { "unicode-segmenter": "^0.14.5" } }, "sha512-MRgJXkx67znuBXuoAYCJkBZyd3OApL7zZlNf5kXhuoCXcdiu1nblRDycYTADSkym4epBSQWxh26kmI9sewaq6A=="], 72 83
+4 -1
package.json
··· 25 25 }, 26 26 "devDependencies": { 27 27 "@atproto/dev-env": "0.3.213", 28 + "@atproto/identity": "^0.4.12", 28 29 "@atproto/sync": "^0.1.40", 29 30 "@biomejs/biome": "^2.4.4", 30 31 "@tailwindcss/cli": "^4.2.1", ··· 52 53 }, 53 54 "dependencies": { 54 55 "@atcute/cid": "^2.4.1", 56 + "@atcute/identity-resolver": "^1.2.2", 57 + "@atcute/identity-resolver-node": "^1.0.3", 55 58 "@atcute/jetstream": "^1.1.2", 59 + "@atcute/lexicons": "^1.3.0", 56 60 "@atcute/tid": "^1.1.2", 57 61 "@atproto/api": "^0.13.0", 58 - "@atproto/identity": "^0.4.12", 59 62 "@atproto/jwk-jose": "^0.1.0", 60 63 "@atproto/oauth-client-node": "^0.1.0", 61 64 "@codemirror/lang-markdown": "^6.5.0",
+48 -9
src/lib/identity.ts
··· 1 - import { IdResolver } from "@atproto/identity"; 1 + import { 2 + CompositeDidDocumentResolver, 3 + CompositeHandleResolver, 4 + type DidDocumentResolver, 5 + type HandleResolver, 6 + PlcDidDocumentResolver, 7 + WebDidDocumentResolver, 8 + WellKnownHandleResolver, 9 + } from "@atcute/identity-resolver"; 10 + import { NodeDnsHandleResolver } from "@atcute/identity-resolver-node"; 11 + import type { Did } from "@atcute/lexicons/syntax"; 2 12 import { getDevPlcUrl } from "../atproto/env.ts"; 3 13 4 - let instance: IdResolver | null = null; 14 + let handleInstance: HandleResolver | null = null; 15 + let didDocInstance: DidDocumentResolver | null = null; 16 + 17 + export function getHandleResolver(): HandleResolver { 18 + if (!handleInstance) { 19 + handleInstance = new CompositeHandleResolver({ 20 + methods: { 21 + dns: new NodeDnsHandleResolver(), 22 + http: new WellKnownHandleResolver(), 23 + }, 24 + }); 25 + } 26 + return handleInstance; 27 + } 5 28 6 - export function getIdResolver(): IdResolver { 7 - if (!instance) { 29 + export function getDidDocumentResolver(): DidDocumentResolver { 30 + if (!didDocInstance) { 8 31 const plcUrl = getDevPlcUrl(); 9 - instance = new IdResolver(plcUrl ? { plcUrl } : {}); 32 + didDocInstance = new CompositeDidDocumentResolver({ 33 + methods: { 34 + plc: new PlcDidDocumentResolver( 35 + plcUrl ? { apiUrl: plcUrl } : undefined, 36 + ), 37 + web: new WebDidDocumentResolver(), 38 + }, 39 + }); 10 40 } 11 - return instance; 41 + return didDocInstance; 42 + } 43 + 44 + function matchesAtprotoPds(type: string | string[]): boolean { 45 + if (typeof type === "string") return type === "AtprotoPersonalDataServer"; 46 + return type.includes("AtprotoPersonalDataServer"); 12 47 } 13 48 14 49 /** ··· 16 51 * Returns null if the DID or PDS service cannot be found. 17 52 */ 18 53 export async function resolvePdsEndpoint(did: string): Promise<string | null> { 19 - const didDoc = await getIdResolver().did.resolve(did); 20 - if (!didDoc) return null; 54 + let didDoc: Awaited<ReturnType<DidDocumentResolver["resolve"]>>; 55 + try { 56 + didDoc = await getDidDocumentResolver().resolve(did as Did); 57 + } catch { 58 + return null; 59 + } 21 60 const service = didDoc.service?.find( 22 - (s) => s.id === "#atproto_pds" || s.type === "AtprotoPersonalDataServer", 61 + (s) => s.id === "#atproto_pds" || matchesAtprotoPds(s.type), 23 62 ); 24 63 if (!service || typeof service.serviceEndpoint !== "string") return null; 25 64 return service.serviceEndpoint;
+5 -5
src/lib/profile.ts
··· 1 + import type { Did, Handle } from "@atcute/lexicons/syntax"; 1 2 import { 2 3 getCachedProfile, 3 4 setCachedProfile, 4 5 } from "../server/db/queries/index.ts"; 5 - import { getIdResolver } from "./identity.ts"; 6 + import { getDidDocumentResolver, getHandleResolver } from "./identity.ts"; 6 7 7 8 export interface ProfileInfo { 8 9 handle: string | null; ··· 23 24 : handleOrDid; 24 25 if (normalized.startsWith("did:")) return normalized; 25 26 try { 26 - const did = await getIdResolver().handle.resolve(normalized); 27 - return did ?? null; 27 + return await getHandleResolver().resolve(normalized as Handle); 28 28 } catch { 29 29 return null; 30 30 } ··· 56 56 } 57 57 58 58 try { 59 - const doc = await getIdResolver().did.resolve(did); 60 - const alsoKnownAs = doc?.alsoKnownAs ?? []; 59 + const doc = await getDidDocumentResolver().resolve(did as Did); 60 + const alsoKnownAs = doc.alsoKnownAs ?? []; 61 61 const atHandle = alsoKnownAs.find((uri) => uri.startsWith("at://")); 62 62 const handle = atHandle ? atHandle.slice("at://".length) : null; 63 63
+2 -2
tests/lib/pds-fetch.test.ts
··· 3 3 import { LIMITS } from "../../src/lib/limits.ts"; 4 4 import { BlobFetchError, fetchVerifiedBlob } from "../../src/lib/pds-fetch.ts"; 5 5 6 - // resolveEndpoint and fetchFn are passed as DI, not module-mocked, to avoid 7 - // conflicting with other test files that mock @atproto/identity. 6 + // resolveEndpoint and fetchFn are passed as DI, not module-mocked, to keep 7 + // this file independent of how identity.ts is mocked elsewhere. 8 8 const mockResolvePdsEndpoint = mock( 9 9 async (_did: string): Promise<string | null> => null, 10 10 );
+7 -6
tests/lib/profile.test.ts
··· 1 1 import { describe, expect, mock, test } from "bun:test"; 2 2 3 - // Mock the IdResolver before importing the module 3 + // Mock identity.ts directly so we don't fight with @atcute/identity-resolver's 4 + // constructor surface. pds-fetch.test.ts uses dependency injection instead, so 5 + // the two mocking approaches don't conflict. 4 6 const mockResolveHandle = mock(async () => "did:plc:resolved"); 5 7 const mockResolveDid = mock(async () => ({ 6 8 alsoKnownAs: ["at://alice.bsky.social"], 7 9 })); 8 10 9 - mock.module("@atproto/identity", () => ({ 10 - IdResolver: class { 11 - handle = { resolve: mockResolveHandle }; 12 - did = { resolve: mockResolveDid }; 13 - }, 11 + mock.module("../../src/lib/identity.ts", () => ({ 12 + getHandleResolver: () => ({ resolve: mockResolveHandle }), 13 + getDidDocumentResolver: () => ({ resolve: mockResolveDid }), 14 + resolvePdsEndpoint: async () => null, 14 15 })); 15 16 16 17 const { resolveHandleToDid, resolveProfile, resolveProfiles } = await import(