Skip to content

Commit

Permalink
feat: implement host-meta lookup for social inbox (#15)
Browse files Browse the repository at this point in the history
  • Loading branch information
akhileshthite committed Nov 14, 2023
1 parent 9c28117 commit 0a4a7c4
Showing 1 changed file with 42 additions and 21 deletions.
63 changes: 42 additions & 21 deletions src/server/apsystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,16 @@ export interface BasicFetchParams {
body?: string
}

export interface HostMetaLink {
rel: string
template?: string
href?: string
}

export interface HostMeta {
links: HostMetaLink[]
}

export type FetchLike = typeof globalThis.fetch

export default class ActivityPubSystem {
Expand Down Expand Up @@ -257,37 +267,48 @@ export default class ActivityPubSystem {

async mentionToActor (mention: string): Promise<string> {
const { username, domain } = parseMention(mention)
const acct = `acct:${username}@${domain}`
// TODO: dynamically determine the parameter name from the host-meta file
const mentionURL = `https://${domain}/.well-known/webfinger?resource=${acct}`
let webfingerURL = `https://${domain}/.well-known/webfinger?resource=acct:${username}@${domain}`

const response = await this.fetch(mentionURL)
let response = await this.fetch(webfingerURL)

// throq if response not ok or if inbox isn't a string
if (!response.ok) {
throw new Error(`Cannot fetch webmention data from ${mentionURL}: http status ${response.status} - ${await response.text()}`)
if (!response.ok && response.status === 404) {
const hostMetaURL = `https://${domain}/.well-known/host-meta`
const hostMetaResponse = await this.signedFetch(this.publicURL, {
url: hostMetaURL,
method: 'GET',
headers: {}
})

if (!hostMetaResponse.ok) {
throw new Error(`Cannot fetch host-meta data from ${hostMetaURL}: http status ${hostMetaResponse.status}`)
}

const hostMeta: HostMeta = await hostMetaResponse.json()
const webfingerTemplate = hostMeta.links.find((link: HostMetaLink) => link.rel === 'lrdd' && link.template)?.template

if (typeof webfingerTemplate !== 'string' || webfingerTemplate.length === 0) {
throw new Error(`Webfinger template not found in host-meta data at ${hostMetaURL}`)
}

webfingerURL = webfingerTemplate.replace('{uri}', `acct:${username}@${domain}`)
response = await this.fetch(webfingerURL)
}

const { subject, links } = await response.json().catch((cause) => {
throw new Error(`Unable to parse webmention JSON at ${mentionURL}`, { cause })
})
if (subject !== acct) {
throw new Error(`Webmention endpoint returned invalid subject. Extepcted ${acct} at ${mentionURL}, got ${subject as string}`)
if (!response.ok) {
throw new Error(`Cannot fetch webmention data from ${webfingerURL}: http status ${response.status}`)
}

if (!Array.isArray(links)) {
throw new Error(`Expected links array in webmention endpoint for ${mentionURL}`)
const { subject, links } = await response.json()
if (subject !== `acct:${username}@${domain}`) {
throw new Error(`Webmention endpoint returned invalid subject for ${webfingerURL}`)
}

for (const { rel, type, href } of links) {
if (rel !== 'self') continue
// TODO: Throw an error?
if (typeof type !== 'string') continue
if (!type.includes('application/activity+json') && !type.includes('application/ld+json')) continue
return href
const actorLink = links.find((link: HostMetaLink) => link.rel === 'self')
if (typeof actorLink?.href !== 'string' || actorLink.href.trim().length === 0) {
throw new Error(`Unable to find actor link from webmention at ${webfingerURL}`)
}

throw new Error(`Unable to find ActivityPub link from webmentions at ${mentionURL}`)
return actorLink.href
}

async ingestActivity (fromActor: string, activity: APActivity): Promise<void> {
Expand Down

0 comments on commit 0a4a7c4

Please sign in to comment.