@evanp/activitypub-bot 0.45.9 → 0.45.10

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,13 @@ and this project adheres to
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
+ ## [0.45.10] - 2026-05-09
13
+
14
+ ### Changed
15
+
16
+ - Upgraded nock, ipaddr.js, and mysql2.
17
+ - inbox and sharedInbox caches are now persistent and shared across servers.
18
+
12
19
  ## [0.45.9] - 2026-04-29
13
20
 
14
21
  ### Fixed
@@ -1,7 +1,5 @@
1
1
  import assert from 'node:assert'
2
2
 
3
- import { LRUCache } from 'lru-cache'
4
-
5
3
  import as2 from './activitystreams.js'
6
4
 
7
5
  const NS = 'https://www.w3.org/ns/activitystreams#'
@@ -11,11 +9,13 @@ const COLLECTION_TYPES = [
11
9
  `${NS}OrderedCollection`
12
10
  ]
13
11
 
12
+ const INBOX = 'inbox'
13
+ const SHARED_INBOX = 'sharedInbox'
14
+
14
15
  export class ActivityDistributor {
15
16
  static #DISTRIBUTION_QUEUE_ID = 'distribution'
16
17
  static #DELIVERY_QUEUE_ID = 'delivery'
17
18
  static #FANOUT_QUEUE_ID = 'fanout'
18
- static #MAX_CACHE_SIZE = 1000000
19
19
  static #PUBLIC = [
20
20
  'https://www.w3.org/ns/activitystreams#Public',
21
21
  'as:Public',
@@ -25,24 +25,23 @@ export class ActivityDistributor {
25
25
  #client = null
26
26
  #formatter = null
27
27
  #actorStorage = null
28
- #directInboxCache = null
29
- #sharedInboxCache = null
30
28
  #logger = null
31
29
  #jobQueue = null
30
+ #cache
32
31
 
33
- constructor (client, formatter, actorStorage, logger, jobQueue) {
32
+ constructor (client, formatter, actorStorage, logger, jobQueue, cache) {
34
33
  assert.ok(client)
35
34
  assert.ok(formatter)
36
35
  assert.ok(actorStorage)
37
36
  assert.ok(logger)
38
37
  assert.ok(jobQueue)
38
+ assert.ok(cache)
39
39
  this.#client = client
40
40
  this.#formatter = formatter
41
41
  this.#actorStorage = actorStorage
42
42
  this.#logger = logger.child({ class: this.constructor.name })
43
43
  this.#jobQueue = jobQueue
44
- this.#directInboxCache = new LRUCache({ max: ActivityDistributor.#MAX_CACHE_SIZE })
45
- this.#sharedInboxCache = new LRUCache({ max: ActivityDistributor.#MAX_CACHE_SIZE })
44
+ this.#cache = cache
46
45
  }
47
46
 
48
47
  async distribute (activity, username) {
@@ -189,7 +188,7 @@ export class ActivityDistributor {
189
188
  assert.ok(username)
190
189
  assert.equal(typeof username, 'string')
191
190
 
192
- let sharedInbox = this.#sharedInboxCache.get(actorId)
191
+ let sharedInbox = await this.#cache.get(actorId, SHARED_INBOX)
193
192
 
194
193
  if (sharedInbox) {
195
194
  return sharedInbox
@@ -202,20 +201,15 @@ export class ActivityDistributor {
202
201
  const endpoints = obj.get('endpoints')
203
202
  if (endpoints) {
204
203
  const firstEndpoint = Array.from(endpoints)[0]
205
- const sharedInboxEndpoint = firstEndpoint.get('sharedInbox')
204
+ const sharedInboxEndpoint = firstEndpoint.get(SHARED_INBOX)
206
205
  if (sharedInboxEndpoint) {
207
206
  const firstSharedInbox = Array.from(sharedInboxEndpoint)[0]
208
207
  sharedInbox = firstSharedInbox.id
209
- this.#sharedInboxCache.set(actorId, sharedInbox)
208
+ await this.#cache.set(actorId, SHARED_INBOX, sharedInbox)
210
209
  return sharedInbox
211
210
  }
212
211
  }
213
212
 
214
- let directInbox = this.#directInboxCache.get(actorId)
215
- if (directInbox) {
216
- return directInbox
217
- }
218
-
219
213
  if (!obj.inbox) {
220
214
  return null
221
215
  }
@@ -223,8 +217,8 @@ export class ActivityDistributor {
223
217
  if (inboxes.length === 0) {
224
218
  return null
225
219
  }
226
- directInbox = inboxes[0].id
227
- this.#directInboxCache.set(actorId, directInbox)
220
+ const directInbox = inboxes[0].id
221
+ await this.#cache.set(actorId, INBOX, directInbox)
228
222
  return directInbox
229
223
  }
230
224
 
@@ -233,7 +227,7 @@ export class ActivityDistributor {
233
227
  assert.equal(typeof actorId, 'string')
234
228
  assert.ok(username)
235
229
  assert.equal(typeof username, 'string')
236
- let directInbox = this.#directInboxCache.get(actorId)
230
+ let directInbox = await this.#cache.get(actorId, INBOX)
237
231
  if (directInbox) {
238
232
  return directInbox
239
233
  }
@@ -248,7 +242,7 @@ export class ActivityDistributor {
248
242
  return null
249
243
  }
250
244
  directInbox = inboxes[0].id
251
- this.#directInboxCache.set(actorId, directInbox)
245
+ await this.#cache.set(actorId, INBOX, directInbox)
252
246
  return directInbox
253
247
  }
254
248
 
package/lib/app.js CHANGED
@@ -10,6 +10,7 @@ import Logger from 'pino'
10
10
  import HTTPLogger from 'pino-http'
11
11
  import { RedisStore } from 'rate-limit-redis'
12
12
  import { createClient } from 'redis'
13
+ import { rateLimit } from 'express-rate-limit'
13
14
 
14
15
  import { ActivityDistributor } from './activitydistributor.js'
15
16
  import { ActivityPubClient, ActivityPubClientError } from './activitypubclient.js'
@@ -52,7 +53,7 @@ import { RemoteObjectCache } from './remoteobjectcache.js'
52
53
  import { HTTPMessageSignature } from './httpmessagesignature.js'
53
54
  import { SignaturePolicyStorage } from './signaturepolicystorage.js'
54
55
  import { SafeAgent } from './safeagent.js'
55
- import { rateLimit } from 'express-rate-limit'
56
+ import { EndpointCache } from './endpointcache.js'
56
57
 
57
58
  const currentDir = dirname(fileURLToPath(import.meta.url))
58
59
  const DEFAULT_INDEX_FILENAME = resolve(currentDir, '..', 'web', 'index.html')
@@ -105,12 +106,14 @@ export async function makeApp ({ databaseUrl, origin, bots, logLevel = 'silent',
105
106
  const remoteKeyStorage = new RemoteKeyStorage(client, connection, logger)
106
107
  const signature = new HTTPSignatureAuthenticator(remoteKeyStorage, signer, messageSigner, digester, logger)
107
108
  const jobQueue = new JobQueue(connection, logger)
109
+ const endpointCache = new EndpointCache(connection, logger)
108
110
  const distributor = new ActivityDistributor(
109
111
  client,
110
112
  formatter,
111
113
  actorStorage,
112
114
  logger,
113
- jobQueue
115
+ jobQueue,
116
+ endpointCache
114
117
  )
115
118
  const authorizer = new Authorizer(actorStorage, formatter, client)
116
119
  const cache = new ObjectCache({
@@ -0,0 +1,57 @@
1
+ import assert from 'node:assert'
2
+
3
+ const EXPIRY = 7 * 24 * 60 * 60 * 1000
4
+
5
+ export class EndpointCache {
6
+ #connection
7
+ #logger
8
+
9
+ constructor (connection, logger) {
10
+ assert.ok(connection)
11
+ assert.equal(typeof connection, 'object')
12
+ assert.ok(logger)
13
+ assert.equal(typeof logger, 'object')
14
+ this.#connection = connection
15
+ this.#logger = logger
16
+ }
17
+
18
+ async get (actorId, name) {
19
+ assert.ok(actorId)
20
+ assert.equal(typeof actorId, 'string')
21
+ assert.ok(name)
22
+ assert.equal(typeof name, 'string')
23
+ const [rows] = await this.#connection.query(
24
+ `SELECT url, expiry FROM endpoint_cache
25
+ WHERE actor_id = ? AND name = ?`,
26
+ { replacements: [actorId, name] }
27
+ )
28
+ if (rows && rows.length > 0) {
29
+ if (new Date(rows[0].expiry) > new Date()) {
30
+ return rows[0].url
31
+ } else {
32
+ return null
33
+ }
34
+ }
35
+ return null
36
+ }
37
+
38
+ async set (actorId, name, url) {
39
+ assert.ok(actorId)
40
+ assert.equal(typeof actorId, 'string')
41
+ assert.ok(name)
42
+ assert.equal(typeof name, 'string')
43
+ assert.ok(url)
44
+ assert.equal(typeof url, 'string')
45
+ await this.#connection.query(
46
+ `
47
+ INSERT INTO endpoint_cache (actor_id, name, url, expiry)
48
+ VALUES (?, ?, ?, ?)
49
+ ON CONFLICT (actor_id, name) DO UPDATE SET
50
+ url = EXCLUDED.url,
51
+ expiry = EXCLUDED.expiry,
52
+ updated_at = CURRENT_TIMESTAMP
53
+ `,
54
+ { replacements: [actorId, name, url, new Date(Date.now() + EXPIRY)] }
55
+ )
56
+ }
57
+ }
@@ -0,0 +1,21 @@
1
+ export const id = '010-endpoint-cache'
2
+
3
+ export async function up (connection, queryOptions = {}) {
4
+ await connection.query(`
5
+ CREATE TABLE endpoint_cache (
6
+ actor_id varchar(512) NOT NULL,
7
+ name varchar(64) NOT NULL,
8
+ url varchar(512) NOT NULL,
9
+ expiry TIMESTAMP NOT NULL,
10
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
11
+ updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
12
+ PRIMARY KEY (actor_id, name)
13
+ );
14
+ `, queryOptions)
15
+ await connection.query(`
16
+ CREATE INDEX endpoint_cache_expiry on endpoint_cache (expiry);
17
+ `, queryOptions)
18
+ await connection.query(`
19
+ CREATE INDEX endpoint_cache_url on endpoint_cache (url);
20
+ `, queryOptions)
21
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evanp/activitypub-bot",
3
- "version": "0.45.9",
3
+ "version": "0.45.10",
4
4
  "description": "server-side ActivityPub bot framework",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",