@takosjp/yurucommu-core 3.4.3 → 3.4.4
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/migrations/0023_delivery_resolution_outbox.sql +33 -0
- package/migrations/0024_delivery_fanout_outbox.sql +35 -0
- package/migrations/0025_delivery_endpoint_terminal_retention.sql +6 -0
- package/migrations/0026_remote_actor_fetch_failures.sql +29 -0
- package/migrations/0027_remote_actor_tombstones.sql +12 -0
- package/migrations/0028_remote_actor_delivery_fence.sql +30 -0
- package/migrations/0029_delivery_endpoint_recipients.sql +47 -0
- package/package.json +2 -1
- package/packages/api/package.json +1 -1
- package/packages/api/src/lib/api/communities.ts +2 -0
- package/packages/api/src/lib/api/fetch.ts +69 -13
- package/packages/api/src/lib/api/normalize.ts +32 -10
- package/packages/api/src/lib/api/notifications.ts +4 -1
- package/packages/api/src/lib/rtc-client.ts +127 -29
- package/src/backend/federation-helpers.ts +1 -0
- package/src/backend/index.ts +16 -3
- package/src/backend/lib/account-migration.ts +340 -0
- package/src/backend/lib/activity-delete-cascade.ts +193 -0
- package/src/backend/lib/activitypub-actor-cache.ts +615 -48
- package/src/backend/lib/activitypub-actor-identity-sql.ts +179 -0
- package/src/backend/lib/activitypub-actor-identity.ts +39 -0
- package/src/backend/lib/activitypub-validators.ts +62 -4
- package/src/backend/lib/ap-ids.ts +36 -6
- package/src/backend/lib/ap-verify.ts +7 -17
- package/src/backend/lib/blocklist-purge.ts +676 -42
- package/src/backend/lib/blocklist.ts +278 -39
- package/src/backend/lib/community-visibility.ts +47 -33
- package/src/backend/lib/delivery/fanout-outbox.ts +336 -0
- package/src/backend/lib/delivery/planner.ts +16 -12
- package/src/backend/lib/delivery/queue-batching.ts +389 -145
- package/src/backend/lib/delivery/queue-delivery.ts +47 -25
- package/src/backend/lib/delivery/queue.ts +479 -73
- package/src/backend/lib/delivery/resolution-outbox.ts +456 -0
- package/src/backend/lib/delivery/types.ts +31 -7
- package/src/backend/lib/feed-exclude.ts +40 -28
- package/src/backend/lib/follow-edge-mutations.ts +217 -0
- package/src/backend/lib/notification-eligibility.ts +15 -9
- package/src/backend/lib/notification-push.ts +2 -2
- package/src/backend/lib/oidc-id-token.ts +66 -0
- package/src/backend/lib/personal-actor-moderation.ts +239 -0
- package/src/backend/lib/post-visibility.ts +79 -16
- package/src/backend/lib/remote-activity-id.ts +61 -0
- package/src/backend/lib/unread-counts.ts +3 -0
- package/src/backend/retention.ts +38 -1
- package/src/backend/routes/account-teardown.ts +422 -156
- package/src/backend/routes/activitypub/handlers/actor-inbox-handlers.ts +104 -105
- package/src/backend/routes/activitypub/handlers/inbound-community-scope.ts +121 -0
- package/src/backend/routes/activitypub/handlers/inbound-object-identity.ts +54 -0
- package/src/backend/routes/activitypub/handlers/inbound-reply-target.ts +34 -0
- package/src/backend/routes/activitypub/handlers/inbound-story-projection.ts +438 -0
- package/src/backend/routes/activitypub/handlers/inbox-content-handlers.ts +1121 -806
- package/src/backend/routes/activitypub/handlers/inbox-follow-handlers.ts +118 -153
- package/src/backend/routes/activitypub/handlers/inbox-interaction-handlers.ts +185 -168
- package/src/backend/routes/activitypub/handlers/inbox-shared-helpers.ts +220 -32
- package/src/backend/routes/activitypub/inbound-activity-identity.ts +16 -0
- package/src/backend/routes/activitypub/inbound-activity-reference.ts +116 -0
- package/src/backend/routes/activitypub/inbound-addressing.ts +87 -0
- package/src/backend/routes/activitypub/inbox-addressing.ts +22 -19
- package/src/backend/routes/activitypub/inbox-types.ts +14 -2
- package/src/backend/routes/activitypub/inbox.ts +81 -105
- package/src/backend/routes/activitypub/outbox.ts +0 -0
- package/src/backend/routes/activitypub.ts +6 -5
- package/src/backend/routes/actors-helpers.ts +34 -8
- package/src/backend/routes/actors.ts +328 -164
- package/src/backend/routes/auth-helpers.ts +57 -9
- package/src/backend/routes/auth.ts +10 -2
- package/src/backend/routes/communities/membership-invites.ts +4 -1
- package/src/backend/routes/communities/membership-members.ts +201 -73
- package/src/backend/routes/communities/membership-requests.ts +141 -76
- package/src/backend/routes/communities/membership-shared.ts +228 -21
- package/src/backend/routes/communities/messages.ts +9 -2
- package/src/backend/routes/communities/routes.ts +48 -13
- package/src/backend/routes/dm/contacts.ts +29 -41
- package/src/backend/routes/dm/conversations-helpers.ts +9 -1
- package/src/backend/routes/dm/messages.ts +36 -42
- package/src/backend/routes/dm/read-archive.ts +4 -2
- package/src/backend/routes/dm/requests.ts +62 -54
- package/src/backend/routes/follow-helpers.ts +200 -60
- package/src/backend/routes/follow.ts +146 -140
- package/src/backend/routes/media.ts +20 -94
- package/src/backend/routes/moderation.ts +72 -4
- package/src/backend/routes/notes.ts +3 -4
- package/src/backend/routes/notifications.ts +209 -108
- package/src/backend/routes/posts/delete-cascade.ts +253 -89
- package/src/backend/routes/posts/federation.ts +373 -0
- package/src/backend/routes/posts/interactions.ts +160 -184
- package/src/backend/routes/posts/like-mutation.ts +240 -0
- package/src/backend/routes/posts/post-helpers.ts +112 -162
- package/src/backend/routes/posts/queries.ts +150 -54
- package/src/backend/routes/posts/routes.ts +169 -329
- package/src/backend/routes/posts/transformers.ts +61 -6
- package/src/backend/routes/recommendations.ts +37 -44
- package/src/backend/routes/rtc/index.ts +26 -6
- package/src/backend/routes/search.ts +39 -55
- package/src/backend/routes/stories/interactions.ts +22 -6
- package/src/backend/routes/stories/query-helpers.ts +28 -41
- package/src/backend/routes/stories/routes.ts +125 -108
- package/src/backend/routes/takos-tools/dm.ts +17 -17
- package/src/backend/routes/takos-tools/posts.ts +189 -106
- package/src/backend/routes/takos-tools/search.ts +13 -4
- package/src/backend/routes/takos-tools/timeline.ts +35 -27
- package/src/backend/routes/takos-tools-response.ts +6 -2
- package/src/backend/routes/timeline.ts +5 -5
- package/src/backend/runtime/call-signaling-do.ts +35 -4
- package/src/backend/runtime/one-time-ticket.ts +116 -0
- package/src/backend/runtime/realtime-stream-do.ts +5 -61
- package/src/backend/runtime/signaling-hub.ts +36 -3
- package/src/backend/server.ts +95 -80
- package/src/db/d1-write.ts +67 -43
- package/src/db/schema/federation.ts +42 -1
- package/src/db/schema/messaging.ts +110 -2
- package/src/db/schema.ts +1 -1
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import type { Context } from "hono";
|
|
2
2
|
import { and, eq, sql } from "drizzle-orm";
|
|
3
3
|
import type { Env, Variables } from "../types.ts";
|
|
4
|
-
import type { Database } from "../../db/index.ts";
|
|
4
|
+
import type { D1Statement, Database } from "../../db/index.ts";
|
|
5
5
|
import {
|
|
6
6
|
activities,
|
|
7
7
|
actorCache,
|
|
8
8
|
actors,
|
|
9
9
|
follows,
|
|
10
10
|
inbox,
|
|
11
|
+
runBatch,
|
|
11
12
|
} from "../../db/index.ts";
|
|
12
13
|
import {
|
|
13
14
|
activityApId,
|
|
@@ -16,6 +17,9 @@ import {
|
|
|
16
17
|
isSafeRemoteUrl,
|
|
17
18
|
} from "../federation-helpers.ts";
|
|
18
19
|
import { enqueueDeliveryToActor } from "../lib/delivery/queue.ts";
|
|
20
|
+
import { prepareDeliveryResolutionJobs } from "../lib/delivery/resolution-outbox.ts";
|
|
21
|
+
import { isActorBlockedStrict } from "../lib/blocklist.ts";
|
|
22
|
+
import { isTrustedRemoteActivityId } from "../lib/remote-activity-id.ts";
|
|
19
23
|
import {
|
|
20
24
|
isUniqueConstraintError,
|
|
21
25
|
parseJsonObject,
|
|
@@ -29,10 +33,6 @@ import { logger } from "../lib/logger.ts";
|
|
|
29
33
|
|
|
30
34
|
const log = logger.child({ component: "follow.helpers" });
|
|
31
35
|
|
|
32
|
-
// `.batch` lives only on the concrete D1/libsql subclasses, not the Database
|
|
33
|
-
// union; reach it through a narrow structural cast (matching the other routes).
|
|
34
|
-
type Batchable = { batch: (stmts: unknown[]) => Promise<unknown> };
|
|
35
|
-
|
|
36
36
|
const REMOTE_FETCH_TIMEOUT_MS = 10000;
|
|
37
37
|
|
|
38
38
|
export type HonoContext = Context<{ Bindings: Env; Variables: Variables }>;
|
|
@@ -121,9 +121,16 @@ export function isResponse(
|
|
|
121
121
|
// ---------------------------------------------------------------------------
|
|
122
122
|
|
|
123
123
|
/**
|
|
124
|
-
*
|
|
124
|
+
* Prepare an outbound Activity and its durable first-hop delivery intent so a
|
|
125
|
+
* relationship mutation can co-commit them in one D1 batch.
|
|
125
126
|
*/
|
|
126
|
-
export
|
|
127
|
+
export type PreparedActivityDelivery = {
|
|
128
|
+
readonly id: string;
|
|
129
|
+
readonly statements: readonly [D1Statement, ...D1Statement[]];
|
|
130
|
+
publish(): Promise<void>;
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
export async function prepareActivityDelivery(
|
|
127
134
|
env: Env,
|
|
128
135
|
db: Database,
|
|
129
136
|
baseUrl: string,
|
|
@@ -132,20 +139,119 @@ export async function createAndDeliverActivity(
|
|
|
132
139
|
object: unknown,
|
|
133
140
|
recipientApId: string,
|
|
134
141
|
objectApId?: string | null,
|
|
135
|
-
): Promise<
|
|
142
|
+
): Promise<PreparedActivityDelivery> {
|
|
136
143
|
const id = activityApId(baseUrl, generateId());
|
|
137
144
|
const activity = buildApActivity(type, actorId, object, id);
|
|
145
|
+
const recipientBlocked = await isActorBlockedStrict(db, recipientApId);
|
|
146
|
+
const preparedResolution = recipientBlocked
|
|
147
|
+
? { statements: [] }
|
|
148
|
+
: await prepareDeliveryResolutionJobs(db, [
|
|
149
|
+
{ activityId: id, recipientActorApId: recipientApId },
|
|
150
|
+
]);
|
|
138
151
|
|
|
139
|
-
|
|
140
|
-
|
|
152
|
+
return {
|
|
153
|
+
id,
|
|
154
|
+
statements: [
|
|
155
|
+
db.insert(activities).values({
|
|
156
|
+
apId: id,
|
|
157
|
+
type,
|
|
158
|
+
actorApId: actorId,
|
|
159
|
+
objectApId: objectApId || undefined,
|
|
160
|
+
rawJson: JSON.stringify(activity),
|
|
161
|
+
direction: "outbound",
|
|
162
|
+
}) as D1Statement,
|
|
163
|
+
...preparedResolution.statements,
|
|
164
|
+
],
|
|
165
|
+
async publish() {
|
|
166
|
+
if (recipientBlocked) return;
|
|
167
|
+
try {
|
|
168
|
+
await enqueueDeliveryToActor(env, id, recipientApId);
|
|
169
|
+
} catch (error) {
|
|
170
|
+
log.error("Failed to publish prepared activity delivery wakeup", {
|
|
171
|
+
event: "follow.activity.delivery_wakeup_failed",
|
|
172
|
+
activityType: type,
|
|
173
|
+
activityId: id,
|
|
174
|
+
recipient: recipientApId,
|
|
175
|
+
error,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** Creates an outbound AP activity and durable delivery intent. */
|
|
183
|
+
export async function createAndDeliverActivity(
|
|
184
|
+
env: Env,
|
|
185
|
+
db: Database,
|
|
186
|
+
baseUrl: string,
|
|
187
|
+
type: string,
|
|
188
|
+
actorId: string,
|
|
189
|
+
object: unknown,
|
|
190
|
+
recipientApId: string,
|
|
191
|
+
objectApId?: string | null,
|
|
192
|
+
): Promise<void> {
|
|
193
|
+
const prepared = await prepareActivityDelivery(
|
|
194
|
+
env,
|
|
195
|
+
db,
|
|
196
|
+
baseUrl,
|
|
141
197
|
type,
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
198
|
+
actorId,
|
|
199
|
+
object,
|
|
200
|
+
recipientApId,
|
|
201
|
+
objectApId,
|
|
202
|
+
);
|
|
203
|
+
await runBatch(db, prepared.statements);
|
|
204
|
+
await prepared.publish();
|
|
205
|
+
}
|
|
147
206
|
|
|
148
|
-
|
|
207
|
+
/**
|
|
208
|
+
* Resolve the peer-facing id for a follow edge's stored activity id.
|
|
209
|
+
*
|
|
210
|
+
* Inbound follow edges persist the origin-bound INTERNAL activity id (the
|
|
211
|
+
* inbox stamps `activity.id` before dispatch so no remote string escapes into
|
|
212
|
+
* internal keys). An Accept/Reject crosses the boundary in the OTHER
|
|
213
|
+
* direction: the remote peer matches `object` against the Follow id IT
|
|
214
|
+
* minted, so echoing our internal `.../inbound-<hash>` id leaves the peer's
|
|
215
|
+
* edge pending forever. The original envelope survives in
|
|
216
|
+
* activities.raw_json — echo its id, but only when it is a bounded id on the
|
|
217
|
+
* requester's own origin (the same trust rule the inbox applies on the way
|
|
218
|
+
* in). Anything else falls back to the stored id, which is already correct
|
|
219
|
+
* for local requesters and for the outbound-follow edges we minted ourselves.
|
|
220
|
+
*/
|
|
221
|
+
export async function resolvePeerFollowActivityId(
|
|
222
|
+
db: Database,
|
|
223
|
+
requesterApId: string,
|
|
224
|
+
storedActivityApId: string | null,
|
|
225
|
+
): Promise<string | null> {
|
|
226
|
+
if (!storedActivityApId) return storedActivityApId;
|
|
227
|
+
let rawJson: string | null | undefined;
|
|
228
|
+
try {
|
|
229
|
+
const row = await db
|
|
230
|
+
.select({ rawJson: activities.rawJson })
|
|
231
|
+
.from(activities)
|
|
232
|
+
.where(eq(activities.apId, storedActivityApId))
|
|
233
|
+
.get();
|
|
234
|
+
rawJson = row?.rawJson;
|
|
235
|
+
} catch {
|
|
236
|
+
return storedActivityApId;
|
|
237
|
+
}
|
|
238
|
+
if (!rawJson) return storedActivityApId;
|
|
239
|
+
let wireId: string;
|
|
240
|
+
try {
|
|
241
|
+
const parsed: unknown = JSON.parse(rawJson);
|
|
242
|
+
const candidate =
|
|
243
|
+
parsed && typeof parsed === "object"
|
|
244
|
+
? (parsed as { id?: unknown }).id
|
|
245
|
+
: undefined;
|
|
246
|
+
if (typeof candidate !== "string") return storedActivityApId;
|
|
247
|
+
wireId = candidate;
|
|
248
|
+
} catch {
|
|
249
|
+
return storedActivityApId;
|
|
250
|
+
}
|
|
251
|
+
if (!isTrustedRemoteActivityId(wireId, requesterApId)) {
|
|
252
|
+
return storedActivityApId;
|
|
253
|
+
}
|
|
254
|
+
return wireId;
|
|
149
255
|
}
|
|
150
256
|
|
|
151
257
|
/**
|
|
@@ -161,17 +267,45 @@ export async function deliverResponseIfRemote(
|
|
|
161
267
|
requesterApId: string,
|
|
162
268
|
originalActivityApId: string | null,
|
|
163
269
|
): Promise<void> {
|
|
164
|
-
|
|
165
|
-
await createAndDeliverActivity(
|
|
270
|
+
const prepared = await prepareResponseIfRemote(
|
|
166
271
|
env,
|
|
167
272
|
db,
|
|
168
273
|
baseUrl,
|
|
169
274
|
type,
|
|
170
275
|
actorId,
|
|
276
|
+
requesterApId,
|
|
171
277
|
originalActivityApId,
|
|
278
|
+
);
|
|
279
|
+
if (!prepared) return;
|
|
280
|
+
await runBatch(db, prepared.statements);
|
|
281
|
+
await prepared.publish();
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export async function prepareResponseIfRemote(
|
|
285
|
+
env: Env,
|
|
286
|
+
db: Database,
|
|
287
|
+
baseUrl: string,
|
|
288
|
+
type: "Accept" | "Reject",
|
|
289
|
+
actorId: string,
|
|
290
|
+
requesterApId: string,
|
|
291
|
+
originalActivityApId: string | null,
|
|
292
|
+
): Promise<PreparedActivityDelivery | null> {
|
|
293
|
+
if (isLocal(requesterApId, baseUrl)) return null;
|
|
294
|
+
const peerActivityApId = await resolvePeerFollowActivityId(
|
|
295
|
+
db,
|
|
172
296
|
requesterApId,
|
|
173
297
|
originalActivityApId,
|
|
174
298
|
);
|
|
299
|
+
return await prepareActivityDelivery(
|
|
300
|
+
env,
|
|
301
|
+
db,
|
|
302
|
+
baseUrl,
|
|
303
|
+
type,
|
|
304
|
+
actorId,
|
|
305
|
+
peerActivityApId,
|
|
306
|
+
requesterApId,
|
|
307
|
+
peerActivityApId,
|
|
308
|
+
);
|
|
175
309
|
}
|
|
176
310
|
|
|
177
311
|
// ---------------------------------------------------------------------------
|
|
@@ -238,17 +372,31 @@ export async function handleLocalFollow(
|
|
|
238
372
|
activityApId: id,
|
|
239
373
|
acceptedAt: status === "accepted" ? now : null,
|
|
240
374
|
});
|
|
375
|
+
const activityInsert = db.insert(activities).values({
|
|
376
|
+
apId: id,
|
|
377
|
+
type: "Follow",
|
|
378
|
+
actorApId: actor.ap_id,
|
|
379
|
+
objectApId: targetApId,
|
|
380
|
+
rawJson: JSON.stringify(followActivity),
|
|
381
|
+
direction: "local",
|
|
382
|
+
});
|
|
383
|
+
const inboxInsert = db.insert(inbox).values({
|
|
384
|
+
actorApId: targetApId,
|
|
385
|
+
activityApId: id,
|
|
386
|
+
read: 0,
|
|
387
|
+
});
|
|
241
388
|
|
|
242
389
|
if (status === "accepted") {
|
|
243
|
-
// Co-commit the edge
|
|
244
|
-
//
|
|
245
|
-
//
|
|
246
|
-
//
|
|
247
|
-
//
|
|
248
|
-
//
|
|
249
|
-
//
|
|
390
|
+
// Co-commit the edge, both increments, ActivityPub ledger row, and target
|
|
391
|
+
// inbox notification in ONE batch. A later activity/inbox failure used to
|
|
392
|
+
// return 500 after the accepted relationship and counts had already
|
|
393
|
+
// committed; the route's existing-edge guard then made every retry a 400,
|
|
394
|
+
// permanently hiding the missing activity/notification. The whole local
|
|
395
|
+
// follow is one authority mutation, so a failure leaves nothing behind.
|
|
396
|
+
// Increments stay guarded by NOT EXISTS(edge), evaluated before the
|
|
397
|
+
// in-batch insert, so a concurrent duplicate cannot double-count.
|
|
250
398
|
const edgeAbsent = sql`NOT EXISTS (SELECT 1 FROM ${follows} WHERE ${follows.followerApId} = ${actor.ap_id} AND ${follows.followingApId} = ${targetApId})`;
|
|
251
|
-
await (db
|
|
399
|
+
await runBatch(db, [
|
|
252
400
|
db
|
|
253
401
|
.update(actors)
|
|
254
402
|
.set({ followingCount: sql`${actors.followingCount} + 1` })
|
|
@@ -258,25 +406,15 @@ export async function handleLocalFollow(
|
|
|
258
406
|
.set({ followerCount: sql`${actors.followerCount} + 1` })
|
|
259
407
|
.where(and(eq(actors.apId, targetApId), edgeAbsent)),
|
|
260
408
|
followInsert,
|
|
409
|
+
activityInsert,
|
|
410
|
+
inboxInsert,
|
|
261
411
|
]);
|
|
262
412
|
} else {
|
|
263
|
-
|
|
413
|
+
// A private-account request is the same atomic authority mutation minus
|
|
414
|
+
// accepted-edge counters: never retain an invisible pending request when
|
|
415
|
+
// its activity or inbox notification could not be stored.
|
|
416
|
+
await runBatch(db, [followInsert, activityInsert, inboxInsert]);
|
|
264
417
|
}
|
|
265
|
-
|
|
266
|
-
await db.insert(activities).values({
|
|
267
|
-
apId: id,
|
|
268
|
-
type: "Follow",
|
|
269
|
-
actorApId: actor.ap_id,
|
|
270
|
-
objectApId: targetApId,
|
|
271
|
-
rawJson: JSON.stringify(followActivity),
|
|
272
|
-
direction: "local",
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
await db.insert(inbox).values({
|
|
276
|
-
actorApId: targetApId,
|
|
277
|
-
activityApId: id,
|
|
278
|
-
read: 0,
|
|
279
|
-
});
|
|
280
418
|
} catch (error) {
|
|
281
419
|
if (isUniqueConstraintError(error)) {
|
|
282
420
|
return c.json({ error: "Already following or pending" }, 400);
|
|
@@ -336,25 +474,27 @@ export async function handleRemoteFollow(
|
|
|
336
474
|
return c.json({ error: "Invalid inbox URL" }, 400);
|
|
337
475
|
}
|
|
338
476
|
|
|
339
|
-
const
|
|
340
|
-
|
|
477
|
+
const preparedDelivery = await prepareActivityDelivery(
|
|
478
|
+
c.env,
|
|
479
|
+
db,
|
|
480
|
+
baseUrl,
|
|
481
|
+
"Follow",
|
|
482
|
+
actor.ap_id,
|
|
483
|
+
targetApId,
|
|
484
|
+
targetApId,
|
|
485
|
+
targetApId,
|
|
486
|
+
);
|
|
341
487
|
|
|
342
488
|
try {
|
|
343
|
-
await db
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
type: "Follow",
|
|
353
|
-
actorApId: actor.ap_id,
|
|
354
|
-
objectApId: targetApId,
|
|
355
|
-
rawJson: JSON.stringify(followActivity),
|
|
356
|
-
direction: "outbound",
|
|
357
|
-
});
|
|
489
|
+
await runBatch(db, [
|
|
490
|
+
db.insert(follows).values({
|
|
491
|
+
followerApId: actor.ap_id,
|
|
492
|
+
followingApId: targetApId,
|
|
493
|
+
status: "pending",
|
|
494
|
+
activityApId: preparedDelivery.id,
|
|
495
|
+
}),
|
|
496
|
+
...preparedDelivery.statements,
|
|
497
|
+
]);
|
|
358
498
|
} catch (e) {
|
|
359
499
|
log.error("Failed to create remote follow", {
|
|
360
500
|
event: "follow.remote.create_failed",
|
|
@@ -365,6 +505,6 @@ export async function handleRemoteFollow(
|
|
|
365
505
|
return c.json({ error: "Failed to follow remote actor" }, 500);
|
|
366
506
|
}
|
|
367
507
|
|
|
368
|
-
await
|
|
508
|
+
await preparedDelivery.publish();
|
|
369
509
|
return c.json({ success: true, status: "pending" });
|
|
370
510
|
}
|