From 103b23884fb190452b4abf38a268deda714b5115 Mon Sep 17 00:00:00 2001 From: Julien Calixte Date: Tue, 17 Feb 2026 09:26:26 +0100 Subject: [PATCH] refacto: use microcosm endpoint and change types --- package.json | 3 + pnpm-lock.yaml | 49 +++++++++++++++ src/components/StackedPublicNote.vue | 6 +- src/hooks/usePublicNoteList.hook.ts | 19 ++++-- src/modules/atproto/getAka.ts | 47 -------------- src/modules/atproto/getAuthor.ts | 79 ++++++++++++++++++++++++ src/modules/atproto/getUrl.ts | 13 ++-- src/modules/atproto/withATProtoImages.ts | 4 +- src/views/PublicNoteListByDidView.vue | 6 +- src/views/PublicNoteListView.vue | 6 +- src/views/PublicNoteView.vue | 13 ++-- 11 files changed, 171 insertions(+), 74 deletions(-) delete mode 100644 src/modules/atproto/getAka.ts create mode 100644 src/modules/atproto/getAuthor.ts diff --git a/package.json b/package.json index 9dbb98a..2597dc2 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,8 @@ "generate-pwa-assets": "pwa-assets-generator" }, "dependencies": { + "@better-fetch/fetch": "^1.1.21", + "@better-fetch/logger": "^1.1.21", "@intlify/unplugin-vue-i18n": "^6.0.8", "@octokit/core": "^7.0.6", "@octokit/rest": "^22.0.1", @@ -25,6 +27,7 @@ "@vueuse/components": "^14.2.1", "@vueuse/core": "^13.6.0", "@vueuse/router": "^13.6.0", + "arktype": "^2.1.29", "date-fns": "^4.1.0", "events": "^3.3.0", "font-color-contrast": "^11.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7e5036b..7f7c7f7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,12 @@ importers: .: dependencies: + '@better-fetch/fetch': + specifier: ^1.1.21 + version: 1.1.21 + '@better-fetch/logger': + specifier: ^1.1.21 + version: 1.1.21 '@intlify/unplugin-vue-i18n': specifier: ^6.0.8 version: 6.0.8(@vue/compiler-dom@3.5.28)(eslint@8.57.1)(rollup@2.79.2)(typescript@5.9.3)(vue-i18n@11.1.11(vue@3.5.18(typescript@5.9.3)))(vue@3.5.18(typescript@5.9.3)) @@ -38,6 +44,9 @@ importers: '@vueuse/router': specifier: ^13.6.0 version: 13.6.0(vue-router@4.5.1(vue@3.5.18(typescript@5.9.3)))(vue@3.5.18(typescript@5.9.3)) + arktype: + specifier: ^2.1.29 + version: 2.1.29 date-fns: specifier: ^4.1.0 version: 4.1.0 @@ -239,6 +248,12 @@ packages: peerDependencies: ajv: '>=8' + '@ark/schema@0.56.0': + resolution: {integrity: sha512-ECg3hox/6Z/nLajxXqNhgPtNdHWC9zNsDyskwO28WinoFEnWow4IsERNz9AnXRhTZJnYIlAJ4uGn3nlLk65vZA==} + + '@ark/util@0.56.0': + resolution: {integrity: sha512-BghfRC8b9pNs3vBoDJhcta0/c1J1rsoS1+HgVUreMFPdhz/CRAKReAu57YEllNaSy98rWAdY1gE+gFup7OXpgA==} + '@babel/code-frame@7.27.1': resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} @@ -798,6 +813,12 @@ packages: resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} + '@better-fetch/fetch@1.1.21': + resolution: {integrity: sha512-/ImESw0sskqlVR94jB+5+Pxjf+xBwDZF/N5+y2/q4EqD7IARUTSpPfIo8uf39SYpCxyOCtbyYpUrZ3F/k0zT4A==} + + '@better-fetch/logger@1.1.21': + resolution: {integrity: sha512-E5/sT3kOuQVZbcm89GmxopMzt4rjTNG26pSDPxNEyis69TMyMEYX5BFnm3qopeTPVkehKz8jwCDJoeBlkaXMqg==} + '@braintree/sanitize-url@7.1.1': resolution: {integrity: sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw==} @@ -2195,6 +2216,12 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + arkregex@0.0.5: + resolution: {integrity: sha512-ncYjBdLlh5/QnVsAA8De16Tc9EqmYM7y/WU9j+236KcyYNUXogpz3sC4ATIZYzzLxwI+0sEOaQLEmLmRleaEXw==} + + arktype@2.1.29: + resolution: {integrity: sha512-jyfKk4xIOzvYNayqnD8ZJQqOwcrTOUbIU4293yrzAjA3O1dWh61j71ArMQ6tS/u4pD7vabSPe7nG3RCyoXW6RQ==} + arr-diff@2.0.0: resolution: {integrity: sha512-dtXTVMkh6VkEEA7OhXnN1Ecb8aAGFdZ1LFxtOCoqj4qkyOJMt7+qs6Ahdy6p/NQCPYsRSXXivhSB/J5E9jmYKA==} engines: {node: '>=0.10.0'} @@ -6097,6 +6124,12 @@ snapshots: jsonpointer: 5.0.1 leven: 3.1.0 + '@ark/schema@0.56.0': + dependencies: + '@ark/util': 0.56.0 + + '@ark/util@0.56.0': {} + '@babel/code-frame@7.27.1': dependencies: '@babel/helper-validator-identifier': 7.27.1 @@ -6854,6 +6887,12 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@better-fetch/fetch@1.1.21': {} + + '@better-fetch/logger@1.1.21': + dependencies: + consola: 3.4.2 + '@braintree/sanitize-url@7.1.1': {} '@canvas/image-data@1.1.0': {} @@ -8234,6 +8273,16 @@ snapshots: argparse@2.0.1: {} + arkregex@0.0.5: + dependencies: + '@ark/util': 0.56.0 + + arktype@2.1.29: + dependencies: + '@ark/schema': 0.56.0 + '@ark/util': 0.56.0 + arkregex: 0.0.5 + arr-diff@2.0.0: dependencies: arr-flatten: 1.1.0 diff --git a/src/components/StackedPublicNote.vue b/src/components/StackedPublicNote.vue index 43fe2d8..dd02c68 100644 --- a/src/components/StackedPublicNote.vue +++ b/src/components/StackedPublicNote.vue @@ -7,7 +7,7 @@ import { markdownBuilder } from "@/hooks/useMarkdown.hook" import { computedAsync } from "@vueuse/core" import { getUrl } from "@/modules/atproto/getUrl" import { withATProtoImages } from "@/modules/atproto/withATProtoImages" -import { getUniqueAka } from "@/modules/atproto/getAka" +import { getAuthor } from "@/modules/atproto/getAuthor" import { PublicNoteRecord } from "@/modules/atproto/publicNote.types" import { parseAtUri } from "@/modules/atproto/parseAtUri" @@ -24,7 +24,7 @@ const rkey = computed(() => atUriProps.value.rkey) const index = computed(() => props.index) -const author = computedAsync(async () => getUniqueAka(did.value)) +const author = computedAsync(async () => getAuthor(did.value)) const url = computedAsync(async () => getUrl({ did: did.value, rkey: rkey.value }), ) @@ -47,7 +47,7 @@ const content = computed(() => noteRecord.value?.value.content && author.value ? toHTML( withATProtoImages(noteRecord.value.value.content, { - endpoint: author.value.endpoint, + pds: author.value.pds, did: did.value, }), ) diff --git a/src/hooks/usePublicNoteList.hook.ts b/src/hooks/usePublicNoteList.hook.ts index 22252e1..67c14d6 100644 --- a/src/hooks/usePublicNoteList.hook.ts +++ b/src/hooks/usePublicNoteList.hook.ts @@ -1,4 +1,4 @@ -import { Author, getAka } from "@/modules/atproto/getAka" +import { Author, getAuthors } from "@/modules/atproto/getAuthor" import { PublicNoteListItem } from "@/modules/note/models/Note" import { computedAsync } from "@vueuse/core" import { computed, ref, Ref } from "vue" @@ -28,16 +28,23 @@ export function usePublicNoteList(did?: Ref) { isLoading.value = false } - const aka = computedAsync>(async () => { + const authors = computedAsync>(async () => { if (notes.value.length === 0) { return new Map() } - return getAka(new Set(notes.value.map((n) => n.did))) + return getAuthors(new Set(notes.value.map((n) => n.did))) }, new Map()) - const getAlias = (did: string) => - aka.value.has(did) ? aka.value.get(did)?.alias : "" + const getAuthor = (did: string) => + authors.value.has(did) ? authors.value.get(did)?.handle : "" - return { notes, isLoading, canLoadMore, onLoadMore, aka, getAlias } + return { + notes, + isLoading, + canLoadMore, + onLoadMore, + authors, + getAuthor, + } } diff --git a/src/modules/atproto/getAka.ts b/src/modules/atproto/getAka.ts deleted file mode 100644 index e6b8628..0000000 --- a/src/modules/atproto/getAka.ts +++ /dev/null @@ -1,47 +0,0 @@ -export type Author = { alias: string; endpoint: string } - -const correspondanceCache = new Map() - -export const getUniqueAka = async (did: string): Promise => { - if (correspondanceCache.has(did)) { - return correspondanceCache.get(did) as Author - } - - const response = await fetch(`https://plc.directory/${did}`) - const { - alsoKnownAs: [aka], - service: [{ serviceEndpoint }], - } = await response.json() - - const alias = aka.replace("at://", "") - const author = { alias, endpoint: serviceEndpoint } - - correspondanceCache.set(did, author) - - return author -} - -export const getAka = async (dids: Set) => { - const correspondance = await Promise.all( - [...dids].map(async (did) => { - if (correspondanceCache.has(did)) { - return [did, correspondanceCache.get(did)] as [string, Author] - } - - const response = await fetch(`https://plc.directory/${did}`) - const { - alsoKnownAs: [aka], - service: [{ serviceEndpoint }], - } = await response.json() - - const alias = aka.replace("at://", "") - const author = { alias, endpoint: serviceEndpoint } - - correspondanceCache.set(did, author) - - return [did, author] as [string, Author] - }), - ) - - return new Map(correspondance) -} diff --git a/src/modules/atproto/getAuthor.ts b/src/modules/atproto/getAuthor.ts new file mode 100644 index 0000000..60e8e0b --- /dev/null +++ b/src/modules/atproto/getAuthor.ts @@ -0,0 +1,79 @@ +import { createSchema, createFetch } from "@better-fetch/fetch" +import { type } from "arktype" + +export type Author = { handle: string; pds: string } + +const correspondanceCache = new Map() + +const schema = createSchema( + { + "/xrpc/blue.microcosm.identity.resolveMiniDoc": { + output: type({ + did: "string", + handle: "string", + pds: "string", + signing_key: "string", + }), + query: type({ + identifier: "string", + }), + }, + }, + { strict: true }, +) + +const microcosmSlingshot = createFetch({ + baseURL: "https://slingshot.microcosm.blue", + // plugins: [logger()], + schema, +}) + +export const getAuthor = async (did: string): Promise => { + if (correspondanceCache.has(did)) { + return correspondanceCache.get(did) as Author + } + + try { + const { data: author, error } = await microcosmSlingshot( + "/xrpc/blue.microcosm.identity.resolveMiniDoc", + { query: { identifier: did } }, + ) + + if (!author) { + return null + } + + correspondanceCache.set(did, author) + + return author + } catch (e) { + console.warn(e) + + return null + } +} + +export const getAuthors = async (dids: Set) => { + const correspondance = await Promise.all( + [...dids].map(async (did) => { + if (correspondanceCache.has(did)) { + return [did, correspondanceCache.get(did)] as [string, Author | null] + } + + const { data: author } = await microcosmSlingshot( + "/xrpc/blue.microcosm.identity.resolveMiniDoc", + { query: { identifier: did } }, + ) + + if (!author) { + return [did, null] as [string, Author | null] + } + + correspondanceCache.set(did, author) + + return [did, author] as [string, Author | null] + }), + ) + + return new Map(correspondance) +} diff --git a/src/modules/atproto/getUrl.ts b/src/modules/atproto/getUrl.ts index f951957..bc744f9 100644 --- a/src/modules/atproto/getUrl.ts +++ b/src/modules/atproto/getUrl.ts @@ -1,3 +1,5 @@ +import { getAuthor } from "@/modules/atproto/getAuthor" + const endpointCache = new Map() const getEndpoint = async (did: string) => { @@ -15,10 +17,13 @@ const getEndpoint = async (did: string) => { } export const getUrl = async ({ did, rkey }: { did: string; rkey: string }) => { - const url = new URL( - "/xrpc/com.atproto.repo.getRecord", - await getEndpoint(did), - ) + const author = await getAuthor(did) + + if (!author) { + return null + } + + const url = new URL("/xrpc/com.atproto.repo.getRecord", author.pds) url.searchParams.set("repo", did) url.searchParams.set("collection", "space.remanso.note") url.searchParams.set("rkey", rkey) diff --git a/src/modules/atproto/withATProtoImages.ts b/src/modules/atproto/withATProtoImages.ts index 3353482..72053fe 100644 --- a/src/modules/atproto/withATProtoImages.ts +++ b/src/modules/atproto/withATProtoImages.ts @@ -1,11 +1,11 @@ export const withATProtoImages = ( markdown: string, - { endpoint, did }: { endpoint: string; did: string }, + { pds, did }: { pds: string; did: string }, ): string => { const imageLinkPattern = /!\[([^\]]*)\]\((bafkrei[a-z0-9]+)\)/g return markdown.replace(imageLinkPattern, (_, altText, cid) => { - const imageUrl = new URL("/xrpc/com.atproto.sync.getBlob", endpoint) + const imageUrl = new URL("/xrpc/com.atproto.sync.getBlob", pds) imageUrl.searchParams.set("did", did) imageUrl.searchParams.set("cid", cid) return `![${altText}](${imageUrl.toString()})` diff --git a/src/views/PublicNoteListByDidView.vue b/src/views/PublicNoteListByDidView.vue index d460282..4060350 100644 --- a/src/views/PublicNoteListByDidView.vue +++ b/src/views/PublicNoteListByDidView.vue @@ -1,7 +1,7 @@