@rmdes/indiekit-endpoint-activitypub 1.0.19 → 1.0.21
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/index.js +91 -8
- package/lib/activity-log.js +7 -3
- package/lib/federation-setup.js +74 -14
- package/lib/inbox-listeners.js +38 -2
- package/lib/jf2-to-as2.js +30 -4
- package/package.json +5 -3
package/index.js
CHANGED
|
@@ -41,6 +41,7 @@ const defaults = {
|
|
|
41
41
|
alsoKnownAs: "",
|
|
42
42
|
activityRetentionDays: 90,
|
|
43
43
|
storeRawActivities: false,
|
|
44
|
+
redisUrl: "",
|
|
44
45
|
};
|
|
45
46
|
|
|
46
47
|
export default class ActivityPubEndpoint {
|
|
@@ -261,10 +262,50 @@ export default class ActivityPubEndpoint {
|
|
|
261
262
|
|
|
262
263
|
try {
|
|
263
264
|
const actorUrl = self._getActorUrl();
|
|
265
|
+
const handle = self.options.actor.handle;
|
|
266
|
+
|
|
267
|
+
const ctx = self._federation.createContext(
|
|
268
|
+
new URL(self._publicationUrl),
|
|
269
|
+
{},
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
// For replies, resolve the original post author for proper
|
|
273
|
+
// addressing (CC) and direct inbox delivery
|
|
274
|
+
let replyToActor = null;
|
|
275
|
+
if (properties["in-reply-to"]) {
|
|
276
|
+
try {
|
|
277
|
+
const remoteObject = await ctx.lookupObject(
|
|
278
|
+
new URL(properties["in-reply-to"]),
|
|
279
|
+
);
|
|
280
|
+
if (remoteObject && typeof remoteObject.getAttributedTo === "function") {
|
|
281
|
+
const author = await remoteObject.getAttributedTo();
|
|
282
|
+
const authorActor = Array.isArray(author) ? author[0] : author;
|
|
283
|
+
if (authorActor?.id) {
|
|
284
|
+
replyToActor = {
|
|
285
|
+
url: authorActor.id.href,
|
|
286
|
+
handle: authorActor.preferredUsername || null,
|
|
287
|
+
recipient: authorActor,
|
|
288
|
+
};
|
|
289
|
+
console.info(
|
|
290
|
+
`[ActivityPub] Reply to ${properties["in-reply-to"]} — resolved author: ${replyToActor.url}`,
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
} catch (error) {
|
|
295
|
+
console.warn(
|
|
296
|
+
`[ActivityPub] Could not resolve reply-to author for ${properties["in-reply-to"]}: ${error.message}`,
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
264
301
|
const activity = jf2ToAS2Activity(
|
|
265
302
|
properties,
|
|
266
303
|
actorUrl,
|
|
267
304
|
self._publicationUrl,
|
|
305
|
+
{
|
|
306
|
+
replyToActorUrl: replyToActor?.url,
|
|
307
|
+
replyToActorHandle: replyToActor?.handle,
|
|
308
|
+
},
|
|
268
309
|
);
|
|
269
310
|
|
|
270
311
|
if (!activity) {
|
|
@@ -278,11 +319,6 @@ export default class ActivityPubEndpoint {
|
|
|
278
319
|
return undefined;
|
|
279
320
|
}
|
|
280
321
|
|
|
281
|
-
const ctx = self._federation.createContext(
|
|
282
|
-
new URL(self._publicationUrl),
|
|
283
|
-
{},
|
|
284
|
-
);
|
|
285
|
-
|
|
286
322
|
// Count followers for logging
|
|
287
323
|
const followerCount =
|
|
288
324
|
await self._collections.ap_followers.countDocuments();
|
|
@@ -291,26 +327,50 @@ export default class ActivityPubEndpoint {
|
|
|
291
327
|
`[ActivityPub] Sending ${activity.constructor?.name || "activity"} for ${properties.url} to ${followerCount} followers`,
|
|
292
328
|
);
|
|
293
329
|
|
|
330
|
+
// Send to followers
|
|
294
331
|
await ctx.sendActivity(
|
|
295
|
-
{ identifier:
|
|
332
|
+
{ identifier: handle },
|
|
296
333
|
"followers",
|
|
297
334
|
activity,
|
|
298
335
|
);
|
|
299
336
|
|
|
337
|
+
// For replies, also deliver to the original post author's inbox
|
|
338
|
+
// so their server can thread the reply under the original post
|
|
339
|
+
if (replyToActor?.recipient) {
|
|
340
|
+
try {
|
|
341
|
+
await ctx.sendActivity(
|
|
342
|
+
{ identifier: handle },
|
|
343
|
+
replyToActor.recipient,
|
|
344
|
+
activity,
|
|
345
|
+
);
|
|
346
|
+
console.info(
|
|
347
|
+
`[ActivityPub] Reply delivered to author: ${replyToActor.url}`,
|
|
348
|
+
);
|
|
349
|
+
} catch (error) {
|
|
350
|
+
console.warn(
|
|
351
|
+
`[ActivityPub] Failed to deliver reply to ${replyToActor.url}: ${error.message}`,
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
300
356
|
// Determine activity type name
|
|
301
357
|
const typeName =
|
|
302
358
|
activity.constructor?.name || "Create";
|
|
359
|
+
const replyNote = replyToActor
|
|
360
|
+
? ` (reply to ${replyToActor.url})`
|
|
361
|
+
: "";
|
|
303
362
|
|
|
304
363
|
await logActivity(self._collections.ap_activities, {
|
|
305
364
|
direction: "outbound",
|
|
306
365
|
type: typeName,
|
|
307
366
|
actorUrl: self._publicationUrl,
|
|
308
367
|
objectUrl: properties.url,
|
|
309
|
-
|
|
368
|
+
targetUrl: replyToActor?.url || undefined,
|
|
369
|
+
summary: `Sent ${typeName} for ${properties.url} to ${followerCount} followers${replyNote}`,
|
|
310
370
|
});
|
|
311
371
|
|
|
312
372
|
console.info(
|
|
313
|
-
`[ActivityPub] Syndication queued: ${typeName} for ${properties.url}`,
|
|
373
|
+
`[ActivityPub] Syndication queued: ${typeName} for ${properties.url}${replyNote}`,
|
|
314
374
|
);
|
|
315
375
|
|
|
316
376
|
return properties.url || undefined;
|
|
@@ -558,6 +618,28 @@ export default class ActivityPubEndpoint {
|
|
|
558
618
|
);
|
|
559
619
|
}
|
|
560
620
|
|
|
621
|
+
// Performance indexes for inbox handlers and batch refollow
|
|
622
|
+
this._collections.ap_followers.createIndex(
|
|
623
|
+
{ actorUrl: 1 },
|
|
624
|
+
{ unique: true, background: true },
|
|
625
|
+
);
|
|
626
|
+
this._collections.ap_following.createIndex(
|
|
627
|
+
{ actorUrl: 1 },
|
|
628
|
+
{ unique: true, background: true },
|
|
629
|
+
);
|
|
630
|
+
this._collections.ap_following.createIndex(
|
|
631
|
+
{ source: 1 },
|
|
632
|
+
{ background: true },
|
|
633
|
+
);
|
|
634
|
+
this._collections.ap_activities.createIndex(
|
|
635
|
+
{ objectUrl: 1 },
|
|
636
|
+
{ background: true },
|
|
637
|
+
);
|
|
638
|
+
this._collections.ap_activities.createIndex(
|
|
639
|
+
{ type: 1, actorUrl: 1, objectUrl: 1 },
|
|
640
|
+
{ background: true },
|
|
641
|
+
);
|
|
642
|
+
|
|
561
643
|
// Seed actor profile from config on first run
|
|
562
644
|
this._seedProfile().catch((error) => {
|
|
563
645
|
console.warn("[ActivityPub] Profile seed failed:", error.message);
|
|
@@ -569,6 +651,7 @@ export default class ActivityPubEndpoint {
|
|
|
569
651
|
mountPath: this.options.mountPath,
|
|
570
652
|
handle: this.options.actor.handle,
|
|
571
653
|
storeRawActivities: this.options.storeRawActivities,
|
|
654
|
+
redisUrl: this.options.redisUrl,
|
|
572
655
|
});
|
|
573
656
|
|
|
574
657
|
this._federation = federation;
|
package/lib/activity-log.js
CHANGED
|
@@ -19,12 +19,16 @@
|
|
|
19
19
|
* @param {string} [record.content] - Content excerpt
|
|
20
20
|
* @param {string} record.summary - Human-readable summary
|
|
21
21
|
*/
|
|
22
|
-
export async function logActivity(collection, record) {
|
|
22
|
+
export async function logActivity(collection, record, options = {}) {
|
|
23
23
|
try {
|
|
24
|
-
|
|
24
|
+
const doc = {
|
|
25
25
|
...record,
|
|
26
26
|
receivedAt: new Date().toISOString(),
|
|
27
|
-
}
|
|
27
|
+
};
|
|
28
|
+
if (options.rawJson) {
|
|
29
|
+
doc.rawJson = options.rawJson;
|
|
30
|
+
}
|
|
31
|
+
await collection.insertOne(doc);
|
|
28
32
|
} catch (error) {
|
|
29
33
|
console.warn("[ActivityPub] Failed to log activity:", error.message);
|
|
30
34
|
}
|
package/lib/federation-setup.js
CHANGED
|
@@ -15,10 +15,14 @@ import {
|
|
|
15
15
|
Person,
|
|
16
16
|
PropertyValue,
|
|
17
17
|
createFederation,
|
|
18
|
+
exportJwk,
|
|
18
19
|
generateCryptoKeyPair,
|
|
20
|
+
importJwk,
|
|
19
21
|
importSpki,
|
|
20
22
|
} from "@fedify/fedify";
|
|
21
23
|
import { configure, getConsoleSink } from "@logtape/logtape";
|
|
24
|
+
import { RedisMessageQueue } from "@fedify/redis";
|
|
25
|
+
import Redis from "ioredis";
|
|
22
26
|
import { MongoKvStore } from "./kv-store.js";
|
|
23
27
|
import { registerInboxListeners } from "./inbox-listeners.js";
|
|
24
28
|
|
|
@@ -41,6 +45,7 @@ export function setupFederation(options) {
|
|
|
41
45
|
mountPath,
|
|
42
46
|
handle,
|
|
43
47
|
storeRawActivities = false,
|
|
48
|
+
redisUrl = "",
|
|
44
49
|
} = options;
|
|
45
50
|
|
|
46
51
|
// Configure LogTape for Fedify delivery logging (once per process)
|
|
@@ -64,9 +69,20 @@ export function setupFederation(options) {
|
|
|
64
69
|
});
|
|
65
70
|
}
|
|
66
71
|
|
|
72
|
+
let queue;
|
|
73
|
+
if (redisUrl) {
|
|
74
|
+
queue = new RedisMessageQueue(() => new Redis(redisUrl));
|
|
75
|
+
console.info("[ActivityPub] Using Redis message queue");
|
|
76
|
+
} else {
|
|
77
|
+
queue = new InProcessMessageQueue();
|
|
78
|
+
console.warn(
|
|
79
|
+
"[ActivityPub] Using in-process message queue (not recommended for production)",
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
67
83
|
const federation = createFederation({
|
|
68
84
|
kv: new MongoKvStore(collections.ap_kv),
|
|
69
|
-
queue
|
|
85
|
+
queue,
|
|
70
86
|
});
|
|
71
87
|
|
|
72
88
|
// --- Actor dispatcher ---
|
|
@@ -113,7 +129,7 @@ export function setupFederation(options) {
|
|
|
113
129
|
|
|
114
130
|
if (keyPairs.length > 0) {
|
|
115
131
|
personOptions.publicKey = keyPairs[0].cryptographicKey;
|
|
116
|
-
personOptions.
|
|
132
|
+
personOptions.assertionMethods = keyPairs.map((k) => k.multikey);
|
|
117
133
|
}
|
|
118
134
|
|
|
119
135
|
if (profile.attachments?.length > 0) {
|
|
@@ -141,26 +157,70 @@ export function setupFederation(options) {
|
|
|
141
157
|
|
|
142
158
|
const keyPairs = [];
|
|
143
159
|
|
|
144
|
-
//
|
|
145
|
-
const legacyKey = await collections.ap_keys.findOne({});
|
|
146
|
-
|
|
160
|
+
// --- Legacy RSA key pair (HTTP Signatures) ---
|
|
161
|
+
const legacyKey = await collections.ap_keys.findOne({ type: "rsa" });
|
|
162
|
+
// Fall back to old schema (no type field) for backward compat
|
|
163
|
+
const rsaDoc =
|
|
164
|
+
legacyKey ||
|
|
165
|
+
(await collections.ap_keys.findOne({
|
|
166
|
+
publicKeyPem: { $exists: true },
|
|
167
|
+
}));
|
|
168
|
+
|
|
169
|
+
if (rsaDoc?.publicKeyPem && rsaDoc?.privateKeyPem) {
|
|
147
170
|
try {
|
|
148
|
-
const publicKey = await importSpki(
|
|
149
|
-
const privateKey = await importPkcs8Pem(
|
|
171
|
+
const publicKey = await importSpki(rsaDoc.publicKeyPem);
|
|
172
|
+
const privateKey = await importPkcs8Pem(rsaDoc.privateKeyPem);
|
|
150
173
|
keyPairs.push({ publicKey, privateKey });
|
|
151
174
|
} catch {
|
|
175
|
+
console.warn("[ActivityPub] Could not import legacy RSA keys");
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// --- Ed25519 key pair (Object Integrity Proofs) ---
|
|
180
|
+
// Load from DB or generate + persist on first use
|
|
181
|
+
let ed25519Doc = await collections.ap_keys.findOne({
|
|
182
|
+
type: "ed25519",
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
if (ed25519Doc?.publicKeyJwk && ed25519Doc?.privateKeyJwk) {
|
|
186
|
+
try {
|
|
187
|
+
const publicKey = await importJwk(
|
|
188
|
+
ed25519Doc.publicKeyJwk,
|
|
189
|
+
"public",
|
|
190
|
+
);
|
|
191
|
+
const privateKey = await importJwk(
|
|
192
|
+
ed25519Doc.privateKeyJwk,
|
|
193
|
+
"private",
|
|
194
|
+
);
|
|
195
|
+
keyPairs.push({ publicKey, privateKey });
|
|
196
|
+
} catch (error) {
|
|
152
197
|
console.warn(
|
|
153
|
-
"[ActivityPub] Could not import
|
|
198
|
+
"[ActivityPub] Could not import Ed25519 keys, regenerating:",
|
|
199
|
+
error.message,
|
|
154
200
|
);
|
|
201
|
+
ed25519Doc = null; // Force regeneration below
|
|
155
202
|
}
|
|
156
203
|
}
|
|
157
204
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
205
|
+
if (!ed25519Doc) {
|
|
206
|
+
try {
|
|
207
|
+
const ed25519 = await generateCryptoKeyPair("Ed25519");
|
|
208
|
+
await collections.ap_keys.insertOne({
|
|
209
|
+
type: "ed25519",
|
|
210
|
+
publicKeyJwk: await exportJwk(ed25519.publicKey),
|
|
211
|
+
privateKeyJwk: await exportJwk(ed25519.privateKey),
|
|
212
|
+
createdAt: new Date().toISOString(),
|
|
213
|
+
});
|
|
214
|
+
keyPairs.push(ed25519);
|
|
215
|
+
console.info(
|
|
216
|
+
"[ActivityPub] Generated and persisted Ed25519 key pair",
|
|
217
|
+
);
|
|
218
|
+
} catch (error) {
|
|
219
|
+
console.warn(
|
|
220
|
+
"[ActivityPub] Could not generate Ed25519 key pair:",
|
|
221
|
+
error.message,
|
|
222
|
+
);
|
|
223
|
+
}
|
|
164
224
|
}
|
|
165
225
|
|
|
166
226
|
return keyPairs;
|
package/lib/inbox-listeners.js
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
Like,
|
|
17
17
|
Move,
|
|
18
18
|
Note,
|
|
19
|
+
Reject,
|
|
19
20
|
Remove,
|
|
20
21
|
Undo,
|
|
21
22
|
Update,
|
|
@@ -160,6 +161,37 @@ export function registerInboxListeners(inboxChain, options) {
|
|
|
160
161
|
});
|
|
161
162
|
}
|
|
162
163
|
})
|
|
164
|
+
.on(Reject, async (ctx, reject) => {
|
|
165
|
+
const actorObj = await reject.getActor();
|
|
166
|
+
const actorUrl = actorObj?.id?.href || "";
|
|
167
|
+
if (!actorUrl) return;
|
|
168
|
+
|
|
169
|
+
// Mark rejected follow in ap_following
|
|
170
|
+
const result = await collections.ap_following.findOneAndUpdate(
|
|
171
|
+
{
|
|
172
|
+
actorUrl,
|
|
173
|
+
source: { $in: ["refollow:sent", "microsub-reader"] },
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
$set: {
|
|
177
|
+
source: "rejected",
|
|
178
|
+
rejectedAt: new Date().toISOString(),
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
{ returnDocument: "after" },
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
if (result) {
|
|
185
|
+
const actorName = result.name || result.handle || actorUrl;
|
|
186
|
+
await logActivity(collections, storeRawActivities, {
|
|
187
|
+
direction: "inbound",
|
|
188
|
+
type: "Reject(Follow)",
|
|
189
|
+
actorUrl,
|
|
190
|
+
actorName,
|
|
191
|
+
summary: `${actorName} rejected our Follow`,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
})
|
|
163
195
|
.on(Like, async (ctx, like) => {
|
|
164
196
|
const objectId = (await like.getObject())?.id?.href || "";
|
|
165
197
|
|
|
@@ -324,8 +356,12 @@ export function registerInboxListeners(inboxChain, options) {
|
|
|
324
356
|
* Wrapper around the shared utility that accepts the (collections, storeRaw, record) signature
|
|
325
357
|
* used throughout this file.
|
|
326
358
|
*/
|
|
327
|
-
async function logActivity(collections, storeRaw, record) {
|
|
328
|
-
await logActivityShared(
|
|
359
|
+
async function logActivity(collections, storeRaw, record, rawJson) {
|
|
360
|
+
await logActivityShared(
|
|
361
|
+
collections.ap_activities,
|
|
362
|
+
record,
|
|
363
|
+
storeRaw && rawJson ? { rawJson } : {},
|
|
364
|
+
);
|
|
329
365
|
}
|
|
330
366
|
|
|
331
367
|
// Cached ActivityPub channel ObjectId
|
package/lib/jf2-to-as2.js
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
Hashtag,
|
|
16
16
|
Image,
|
|
17
17
|
Like,
|
|
18
|
+
Mention,
|
|
18
19
|
Note,
|
|
19
20
|
Video,
|
|
20
21
|
} from "@fedify/fedify";
|
|
@@ -126,9 +127,12 @@ export function jf2ToActivityStreams(properties, actorUrl, publicationUrl) {
|
|
|
126
127
|
* @param {object} properties - JF2 post properties
|
|
127
128
|
* @param {string} actorUrl - Actor URL (e.g. "https://example.com/activitypub/users/rick")
|
|
128
129
|
* @param {string} publicationUrl - Publication base URL with trailing slash
|
|
130
|
+
* @param {object} [options] - Optional settings
|
|
131
|
+
* @param {string} [options.replyToActorUrl] - Original post author's actor URL (for reply addressing)
|
|
132
|
+
* @param {string} [options.replyToActorHandle] - Original post author's handle (for Mention tag)
|
|
129
133
|
* @returns {import("@fedify/fedify").Activity | null}
|
|
130
134
|
*/
|
|
131
|
-
export function jf2ToAS2Activity(properties, actorUrl, publicationUrl) {
|
|
135
|
+
export function jf2ToAS2Activity(properties, actorUrl, publicationUrl, options = {}) {
|
|
132
136
|
const postType = properties["post-type"];
|
|
133
137
|
const actorUri = new URL(actorUrl);
|
|
134
138
|
|
|
@@ -154,13 +158,25 @@ export function jf2ToAS2Activity(properties, actorUrl, publicationUrl) {
|
|
|
154
158
|
const isArticle = postType === "article" && properties.name;
|
|
155
159
|
const postUrl = resolvePostUrl(properties.url, publicationUrl);
|
|
156
160
|
const followersUrl = `${actorUrl.replace(/\/$/, "")}/followers`;
|
|
161
|
+
const { replyToActorUrl, replyToActorHandle } = options;
|
|
157
162
|
|
|
158
163
|
const noteOptions = {
|
|
159
164
|
attributedTo: actorUri,
|
|
160
|
-
to: new URL("https://www.w3.org/ns/activitystreams#Public"),
|
|
161
|
-
cc: new URL(followersUrl),
|
|
162
165
|
};
|
|
163
166
|
|
|
167
|
+
// Addressing: for replies, include original author in CC so their server
|
|
168
|
+
// threads the reply and notifies them
|
|
169
|
+
if (replyToActorUrl && properties["in-reply-to"]) {
|
|
170
|
+
noteOptions.to = new URL("https://www.w3.org/ns/activitystreams#Public");
|
|
171
|
+
noteOptions.ccs = [
|
|
172
|
+
new URL(followersUrl),
|
|
173
|
+
new URL(replyToActorUrl),
|
|
174
|
+
];
|
|
175
|
+
} else {
|
|
176
|
+
noteOptions.to = new URL("https://www.w3.org/ns/activitystreams#Public");
|
|
177
|
+
noteOptions.cc = new URL(followersUrl);
|
|
178
|
+
}
|
|
179
|
+
|
|
164
180
|
if (postUrl) {
|
|
165
181
|
noteOptions.id = new URL(postUrl);
|
|
166
182
|
noteOptions.url = new URL(postUrl);
|
|
@@ -208,8 +224,18 @@ export function jf2ToAS2Activity(properties, actorUrl, publicationUrl) {
|
|
|
208
224
|
noteOptions.attachments = fedifyAttachments;
|
|
209
225
|
}
|
|
210
226
|
|
|
211
|
-
//
|
|
227
|
+
// Tags: hashtags + Mention for reply addressing
|
|
212
228
|
const fedifyTags = buildFedifyTags(properties, publicationUrl, postType);
|
|
229
|
+
|
|
230
|
+
if (replyToActorUrl) {
|
|
231
|
+
fedifyTags.push(
|
|
232
|
+
new Mention({
|
|
233
|
+
href: new URL(replyToActorUrl),
|
|
234
|
+
name: replyToActorHandle ? `@${replyToActorHandle}` : undefined,
|
|
235
|
+
}),
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
213
239
|
if (fedifyTags.length > 0) {
|
|
214
240
|
noteOptions.tags = fedifyTags;
|
|
215
241
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rmdes/indiekit-endpoint-activitypub",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.21",
|
|
4
4
|
"description": "ActivityPub federation endpoint for Indiekit via Fedify. Adds full fediverse support: actor, inbox, outbox, followers, following, syndication, and Mastodon migration.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"indiekit",
|
|
@@ -37,10 +37,12 @@
|
|
|
37
37
|
"url": "https://github.com/rmdes/indiekit-endpoint-activitypub/issues"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@fedify/fedify": "^1.10.0",
|
|
41
40
|
"@fedify/express": "^1.9.0",
|
|
41
|
+
"@fedify/fedify": "^1.10.0",
|
|
42
|
+
"@fedify/redis": "^1.10.3",
|
|
42
43
|
"@js-temporal/polyfill": "^0.5.0",
|
|
43
|
-
"express": "^5.0.0"
|
|
44
|
+
"express": "^5.0.0",
|
|
45
|
+
"ioredis": "^5.9.3"
|
|
44
46
|
},
|
|
45
47
|
"peerDependencies": {
|
|
46
48
|
"@indiekit/error": "^1.0.0-beta.25",
|