@evanp/activitypub-bot 0.45.16 → 0.45.18
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 +20 -0
- package/lib/activitypubclient.js +8 -4
- package/lib/jobqueue.js +36 -10
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,26 @@ and this project adheres to
|
|
|
9
9
|
|
|
10
10
|
## [Unreleased]
|
|
11
11
|
|
|
12
|
+
## [0.45.18] - 2026-05-12
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- Changed how AbortSignal is used in JobQueue to prevent
|
|
17
|
+
memory leak.
|
|
18
|
+
|
|
19
|
+
## [0.45.17] - 2026-05-11
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- Bumped activitypub-nock.
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- Look up id in results body even if the the id is not a fragment; fixes
|
|
28
|
+
problems with gotosocial keys.
|
|
29
|
+
- Retry signature code on more HTTP status
|
|
30
|
+
results, for servers that use (wrong, bad) 400, 404 and 410 results for bad signatures.
|
|
31
|
+
|
|
12
32
|
## [0.45.16] - 2026-05-11
|
|
13
33
|
|
|
14
34
|
### Changed
|
package/lib/activitypubclient.js
CHANGED
|
@@ -24,6 +24,8 @@ const COLLECTION_TYPES = [
|
|
|
24
24
|
`${NS}OrderedCollection`
|
|
25
25
|
]
|
|
26
26
|
|
|
27
|
+
const SIG_ERROR_STATUS = [400, 401, 403, 404, 410]
|
|
28
|
+
|
|
27
29
|
function normalizeHeaders (headers) {
|
|
28
30
|
if (!headers) {
|
|
29
31
|
return headers
|
|
@@ -120,7 +122,7 @@ export class ActivityPubClient {
|
|
|
120
122
|
try {
|
|
121
123
|
obj = await this.#get(url, this.#urlFormatter.hostname, false, false)
|
|
122
124
|
} catch (err) {
|
|
123
|
-
if (err.status &&
|
|
125
|
+
if (err.status && SIG_ERROR_STATUS.includes(err.status)) {
|
|
124
126
|
obj = await this.#get(url, this.#urlFormatter.hostname, true, false)
|
|
125
127
|
} else {
|
|
126
128
|
throw err
|
|
@@ -205,7 +207,7 @@ export class ActivityPubClient {
|
|
|
205
207
|
}
|
|
206
208
|
)
|
|
207
209
|
this.#logger.debug({ hostname, status: res.status }, 'response received')
|
|
208
|
-
if (
|
|
210
|
+
if (SIG_ERROR_STATUS.includes(res.status) &&
|
|
209
211
|
sign &&
|
|
210
212
|
lastPolicy === SignaturePolicyStorage.RFC9421) {
|
|
211
213
|
const errBody = await res.text()
|
|
@@ -279,7 +281,9 @@ export class ActivityPubClient {
|
|
|
279
281
|
this.#logger.warn({ url: baseUrl, json }, 'Error importing JSON as AS2')
|
|
280
282
|
throw err
|
|
281
283
|
}
|
|
282
|
-
|
|
284
|
+
// NB: should only test for fragments,
|
|
285
|
+
// but e.g. gotosocial has this.
|
|
286
|
+
const resolved = (obj.id !== url)
|
|
283
287
|
? this.#resolveObject(obj, url)
|
|
284
288
|
: obj
|
|
285
289
|
return resolved
|
|
@@ -345,7 +349,7 @@ export class ActivityPubClient {
|
|
|
345
349
|
body
|
|
346
350
|
}
|
|
347
351
|
)
|
|
348
|
-
if (
|
|
352
|
+
if (SIG_ERROR_STATUS.includes(res.status) &&
|
|
349
353
|
lastPolicy === SignaturePolicyStorage.RFC9421) {
|
|
350
354
|
const errBody = await res.text()
|
|
351
355
|
this.#logger.debug(
|
package/lib/jobqueue.js
CHANGED
|
@@ -46,17 +46,25 @@ export class JobQueue {
|
|
|
46
46
|
'dequeueing job'
|
|
47
47
|
)
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
49
|
+
let armed = this.#armCombined()
|
|
50
|
+
try {
|
|
51
|
+
while (!res) {
|
|
52
|
+
res = await this.#claimNextJob(queueId, jobRunnerId)
|
|
53
|
+
if (res) break
|
|
54
|
+
delay = Math.min(delay * 2, MAX_DELAY)
|
|
55
|
+
this.#logger.debug({ method: 'dequeue', delay }, 'sleeping')
|
|
56
|
+
try {
|
|
57
|
+
await sleep(delay, null, { signal: armed.signal })
|
|
58
|
+
} catch (err) {
|
|
59
|
+
if (this.#ac.signal.aborted) throw err
|
|
60
|
+
delay = 50
|
|
61
|
+
// Wake fired — tear down old listeners and arm a fresh combined signal.
|
|
62
|
+
this.#disarmCombined(armed)
|
|
63
|
+
armed = this.#armCombined()
|
|
64
|
+
}
|
|
59
65
|
}
|
|
66
|
+
} finally {
|
|
67
|
+
this.#disarmCombined(armed)
|
|
60
68
|
}
|
|
61
69
|
|
|
62
70
|
const jobId = res.job_id
|
|
@@ -67,6 +75,24 @@ export class JobQueue {
|
|
|
67
75
|
return { jobId, payload, attempts }
|
|
68
76
|
}
|
|
69
77
|
|
|
78
|
+
// Build a combined abort signal manually, avoiding AbortSignal.any() which
|
|
79
|
+
// registers a FinalizationRegistry callback per call (Node's
|
|
80
|
+
// sourceSignalsCleanupRegistry). Under the dequeue loop's call rate, those
|
|
81
|
+
// registrations dominate heap growth.
|
|
82
|
+
#armCombined () {
|
|
83
|
+
const combinedAc = new AbortController()
|
|
84
|
+
const onAbort = () => combinedAc.abort()
|
|
85
|
+
const wakeAc = this.#wakeAc
|
|
86
|
+
this.#ac.signal.addEventListener('abort', onAbort, { once: true })
|
|
87
|
+
wakeAc.signal.addEventListener('abort', onAbort, { once: true })
|
|
88
|
+
return { signal: combinedAc.signal, onAbort, wakeAc }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
#disarmCombined (armed) {
|
|
92
|
+
this.#ac.signal.removeEventListener('abort', armed.onAbort)
|
|
93
|
+
armed.wakeAc.signal.removeEventListener('abort', armed.onAbort)
|
|
94
|
+
}
|
|
95
|
+
|
|
70
96
|
async complete (jobId, jobRunnerId) {
|
|
71
97
|
await this.#connection.query(`
|
|
72
98
|
DELETE FROM job
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evanp/activitypub-bot",
|
|
3
|
-
"version": "0.45.
|
|
3
|
+
"version": "0.45.18",
|
|
4
4
|
"description": "server-side ActivityPub bot framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"sequelize": "^6.37.7"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
|
-
"@evanp/activitypub-nock": "^0.9.
|
|
48
|
+
"@evanp/activitypub-nock": "^0.9.5",
|
|
49
49
|
"eslint": "^8.57.1",
|
|
50
50
|
"eslint-config-standard": "^17.1.0",
|
|
51
51
|
"eslint-plugin-import": "^2.29.1",
|