@evanp/activitypub-bot 0.51.4 → 0.52.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -9,6 +9,25 @@ and this project adheres to
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
+ ## [0.52.0] - 2026-10-05
13
+
14
+ ### Added
15
+
16
+ - `GET /js/ap-components.min.js` to serve the installed ap-components bundle
17
+ locally, with cache revalidation.
18
+ - Regression tests for local component serving, injected fetch usage, reverse
19
+ WebFinger metadata, and discovery failures.
20
+
21
+ ### Changed
22
+
23
+ - Serve ap-components from the installed npm dependency at
24
+ `/js/ap-components.min.js` instead of loading it from a CDN on profile pages.
25
+ - Use activitypub-webfinger for actor ID discovery, mention resolution, and
26
+ reverse WebFinger lookup, with SafeFetcher.fetch bound once per instance.
27
+ - Reverse WebFinger lookups now fetch directly through SafeFetcher instead of
28
+ the cached, signed ActivityPub client.
29
+ - Return `null` on failed WebFinger discovery in `BotContext.toActorId()`.
30
+
12
31
  ## [0.51.4] 2026-09-29
13
32
 
14
33
  ### Updated
@@ -1663,7 +1682,8 @@ Hot patch on the `stable` branch.
1663
1682
  - Docker multi-platform build workflow.
1664
1683
  - Dependabot configuration.
1665
1684
 
1666
- [Unreleased]: https://github.com/evanp/activitypub-bot/compare/v0.51.4...HEAD
1685
+ [Unreleased]: https://github.com/evanp/activitypub-bot/compare/v0.52.0...HEAD
1686
+ [0.52.0]: https://github.com/evanp/activitypub-bot/compare/v0.51.4...v0.52.0
1667
1687
  [0.51.4]: https://github.com/evanp/activitypub-bot/compare/v0.51.3...v0.51.4
1668
1688
  [0.51.3]: https://github.com/evanp/activitypub-bot/compare/v0.51.2...v0.51.3
1669
1689
  [0.51.2]: https://github.com/evanp/activitypub-bot/compare/v0.51.1...v0.51.2
package/lib/app.js CHANGED
@@ -37,6 +37,7 @@ import proxyRouter from './routes/proxy.js'
37
37
  import nodeinfoRouter from './routes/nodeinfo.js'
38
38
  import robotsTxtRouter from './routes/robotstxt.js'
39
39
  import faviconRouter from './routes/favicon.js'
40
+ import staticRouter from './routes/static.js'
40
41
  import { BotContext } from './botcontext.js'
41
42
  import { Transformer } from './microsyntax.js'
42
43
  import { HTTPSignatureAuthenticator } from './httpsignatureauthenticator.js'
@@ -331,6 +332,7 @@ export async function makeApp ({ databaseUrl, origin, bots, logLevel = 'silent',
331
332
 
332
333
  // These should not check HTTP Signature even if provided
333
334
 
335
+ app.use('/', staticRouter)
334
336
  app.use('/', healthRouter)
335
337
  app.use('/', serverRouter)
336
338
  app.use('/', profileRouter)
package/lib/botcontext.js CHANGED
@@ -1,18 +1,12 @@
1
1
  import assert from 'node:assert'
2
2
 
3
3
  import { nanoid } from 'nanoid'
4
+ import { actorIdOf, webfingerOf } from 'activitypub-webfinger'
4
5
 
5
6
  import as2 from './activitystreams.js'
6
7
 
7
- const AS2_TYPES = [
8
- 'application/activity+json',
9
- 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
10
- ]
11
-
12
8
  const AS2_NS = 'https://www.w3.org/ns/activitystreams#'
13
9
 
14
- const WF_NS = 'https://purl.archive.org/socialweb/webfinger#'
15
-
16
10
  const THREAD_PROP = 'https://purl.archive.org/socialweb/thread#thread'
17
11
  const CONVERSATION_PROP = 'http://ostatus.org/schema/1.0/conversation'
18
12
 
@@ -28,6 +22,7 @@ export class BotContext {
28
22
  #logger = null
29
23
  #bots
30
24
  #fetcher
25
+ #fetch
31
26
 
32
27
  constructor (
33
28
  botId,
@@ -64,6 +59,7 @@ export class BotContext {
64
59
  this.#bots = bots
65
60
  this.#logger = logger.child({ class: 'BotContext', botId })
66
61
  this.#fetcher = fetcher
62
+ this.#fetch = fetcher.fetch.bind(fetcher)
67
63
  }
68
64
 
69
65
  get botId () {
@@ -400,34 +396,11 @@ export class BotContext {
400
396
  if (domain.toLowerCase() === this.#formatter.hostname) {
401
397
  return this.#formatter.format({ username })
402
398
  }
403
- const url = `https://${domain}/.well-known/webfinger` +
404
- `?resource=acct:${webfinger}`
405
- const res = await this.#fetcher.fetch(url)
406
- if (res.status !== 200) {
407
- throw new Error(`Status ${res.status} fetching ${url}`)
408
- }
409
- const json = await res.json()
410
- const link = json?.links?.find(
411
- (l) => l.rel === 'self' && AS2_TYPES.includes(l.type)
412
- )
413
- return link ? link.href : null
399
+ return await actorIdOf(webfinger, { fetch: this.#fetch })
414
400
  }
415
401
 
416
402
  async toWebfinger (actorId) {
417
- const actor = await this.#getRemoteObject(actorId)
418
- if (!actor) {
419
- return null
420
- }
421
- const wf = actor.get(WF_NS + 'webfinger')
422
- if (wf) {
423
- return wf.value()
424
- }
425
- const username = actor.get('preferredUsername')?.first
426
- if (!username) {
427
- return null
428
- }
429
- const actorUrl = new URL(actor.id)
430
- return `${username}@${actorUrl.hostname}`
403
+ return await webfingerOf(actorId, { fetch: this.#fetch })
431
404
  }
432
405
 
433
406
  async announceObject (obj, actors = null) {
@@ -1,11 +1,12 @@
1
1
  import assert from 'node:assert'
2
+ import { actorIdOf } from 'activitypub-webfinger'
2
3
 
3
4
  const AS2 = 'https://www.w3.org/ns/activitystreams#'
4
5
 
5
6
  export class Transformer {
6
7
  #tagNamespace = null
7
8
  #client = null
8
- #fetcher = null
9
+ #fetch = null
9
10
  #formatter = null
10
11
 
11
12
  constructor (tagNamespace, client, fetcher, formatter) {
@@ -16,12 +17,12 @@ export class Transformer {
16
17
 
17
18
  this.#tagNamespace = tagNamespace
18
19
  this.#client = client
19
- this.#fetcher = fetcher
20
+ this.#fetch = fetcher.fetch.bind(fetcher)
20
21
  this.#formatter = formatter
21
22
 
22
23
  assert.ok(this.#tagNamespace)
23
24
  assert.ok(this.#client)
24
- assert.ok(this.#fetcher)
25
+ assert.ok(this.#fetch)
25
26
  assert.ok(this.#formatter)
26
27
  }
27
28
 
@@ -99,24 +100,7 @@ export class Transformer {
99
100
  if (domain.toLowerCase() === this.#formatter.hostname) {
100
101
  return this.#formatter.format({ username, type: 'profile' })
101
102
  }
102
- const url = `https://${domain}/.well-known/webfinger?` +
103
- `resource=acct:${username}@${domain}`
104
- let json = null
105
- try {
106
- const response = await this.#fetcher.fetch(url,
107
- { headers: { Accept: 'application/jrd+json' } })
108
- if (response.status !== 200) return null
109
- json = await response.json()
110
- } catch (error) {
111
- return null
112
- }
113
- if (!json.links) return null
114
- const link = json.links.find(
115
- link => link.rel === 'self' &&
116
- (link.type === 'application/activity+json' ||
117
- link.type === 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'))
118
- if (!link) return null
119
- const actorId = link.href
103
+ const actorId = await actorIdOf(webfinger, { fetch: this.#fetch })
120
104
  if (!actorId) return null
121
105
  let actor = null
122
106
  try {
@@ -0,0 +1,12 @@
1
+ import { Router } from 'express'
2
+ import { fileURLToPath } from 'node:url'
3
+
4
+ const router = Router()
5
+
6
+ const COMPONENT_BUNDLE_FILENAME = fileURLToPath(import.meta.resolve('@socialwebfoundation/ap-components/dist/ap-components.min.js'))
7
+
8
+ router.get('/js/ap-components.min.js', (req, res) => {
9
+ res.sendFile(COMPONENT_BUNDLE_FILENAME, { maxAge: 0 })
10
+ })
11
+
12
+ export default router
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evanp/activitypub-bot",
3
- "version": "0.51.4",
3
+ "version": "0.52.0",
4
4
  "description": "server-side ActivityPub bot framework",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -31,6 +31,8 @@
31
31
  "homepage": "https://github.com/evanp/activitypub-bot#readme",
32
32
  "dependencies": {
33
33
  "@isaacs/ttlcache": "^2.1.4",
34
+ "@socialwebfoundation/ap-components": "^1.4.2",
35
+ "activitypub-webfinger": "^0.2.1",
34
36
  "activitystrea.ms": "^4.0.1",
35
37
  "csv-parse": "^7.0.0",
36
38
  "express": "^5.2.1",
package/web/profile.html CHANGED
@@ -6,7 +6,6 @@
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
7
  <meta name="robots" content="noindex,nofollow">
8
8
  <title>activitypub.bot</title>
9
- <script type="module" src="https://unpkg.com/@socialwebfoundation/ap-components"></script>
10
9
  <style>
11
10
  body { max-width: 60ch; margin: 0 auto; padding: 1rem; }
12
11
  </style>
@@ -19,7 +18,7 @@
19
18
  </main>
20
19
 
21
20
  <script type="module">
22
- import { ActivityPubElement } from "https://unpkg.com/@socialwebfoundation/ap-components";
21
+ import { ActivityPubElement } from "/js/ap-components.min.js";
23
22
 
24
23
  const cache = new Map();
25
24