@commonpub/layer 0.7.1 → 0.7.2
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/package.json +8 -8
- package/pages/federated-hubs/[id]/posts/[postId].vue +91 -13
- package/pages/hubs/[slug]/index.vue +1 -1
- package/pages/hubs/[slug]/posts/[postId].vue +12 -0
- package/server/api/federated-hubs/[id]/posts/[postId]/replies.get.ts +13 -0
- package/server/api/federation/hub-post-reply.post.ts +16 -10
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@commonpub/layer",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./nuxt.config.ts",
|
|
6
6
|
"files": [
|
|
@@ -50,16 +50,16 @@
|
|
|
50
50
|
"vue": "^3.4.0",
|
|
51
51
|
"vue-router": "^4.3.0",
|
|
52
52
|
"zod": "^4.3.6",
|
|
53
|
-
"@commonpub/auth": "0.5.0",
|
|
54
53
|
"@commonpub/config": "0.8.0",
|
|
55
|
-
"@commonpub/editor": "0.5.0",
|
|
56
|
-
"@commonpub/learning": "0.5.0",
|
|
57
54
|
"@commonpub/docs": "0.6.1",
|
|
58
|
-
"@commonpub/
|
|
59
|
-
"@commonpub/ui": "0.8.4",
|
|
55
|
+
"@commonpub/editor": "0.5.0",
|
|
60
56
|
"@commonpub/explainer": "0.7.2",
|
|
61
|
-
"@commonpub/
|
|
62
|
-
"@commonpub/
|
|
57
|
+
"@commonpub/auth": "0.5.0",
|
|
58
|
+
"@commonpub/learning": "0.5.0",
|
|
59
|
+
"@commonpub/protocol": "0.9.7",
|
|
60
|
+
"@commonpub/server": "2.27.2",
|
|
61
|
+
"@commonpub/schema": "0.9.2",
|
|
62
|
+
"@commonpub/ui": "0.8.4"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
65
|
"@testing-library/jest-dom": "^6.9.1",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import type { FederatedHubListItem, FederatedHubPostItem } from '@commonpub/server';
|
|
2
|
+
import type { FederatedHubListItem, FederatedHubPostItem, FederatedHubPostReplyItem } from '@commonpub/server';
|
|
3
3
|
|
|
4
4
|
const route = useRoute();
|
|
5
5
|
const id = route.params.id as string;
|
|
@@ -10,6 +10,9 @@ const toast = useToast();
|
|
|
10
10
|
|
|
11
11
|
const { data: hub } = useLazyFetch<FederatedHubListItem>(`/api/federated-hubs/${id}`);
|
|
12
12
|
const { data: post, refresh: refreshPost } = useLazyFetch<FederatedHubPostItem>(`/api/federated-hubs/${id}/posts/${postId}`);
|
|
13
|
+
const { data: repliesData, refresh: refreshReplies } = useLazyFetch<{ items: FederatedHubPostReplyItem[]; total: number }>(`/api/federated-hubs/${id}/posts/${postId}/replies`, { default: () => ({ items: [], total: 0 }) });
|
|
14
|
+
|
|
15
|
+
const replies = computed(() => repliesData.value?.items ?? []);
|
|
13
16
|
|
|
14
17
|
function formatDate(d: string | Date | null): string {
|
|
15
18
|
if (!d) return '';
|
|
@@ -68,6 +71,7 @@ async function handleLike(): Promise<void> {
|
|
|
68
71
|
// Reply
|
|
69
72
|
const replyContent = ref('');
|
|
70
73
|
const replying = ref(false);
|
|
74
|
+
const replyingTo = ref<string | null>(null);
|
|
71
75
|
|
|
72
76
|
async function handleReply(): Promise<void> {
|
|
73
77
|
if (!replyContent.value.trim()) return;
|
|
@@ -75,11 +79,12 @@ async function handleReply(): Promise<void> {
|
|
|
75
79
|
try {
|
|
76
80
|
await $fetch('/api/federation/hub-post-reply' as string, {
|
|
77
81
|
method: 'POST',
|
|
78
|
-
body: { federatedHubPostId: postId, content: replyContent.value },
|
|
82
|
+
body: { federatedHubPostId: postId, content: replyContent.value, parentId: replyingTo.value || undefined },
|
|
79
83
|
});
|
|
80
84
|
replyContent.value = '';
|
|
81
|
-
|
|
82
|
-
|
|
85
|
+
replyingTo.value = null;
|
|
86
|
+
toast.success('Reply posted');
|
|
87
|
+
await Promise.all([refreshReplies(), refreshPost()]);
|
|
83
88
|
} catch {
|
|
84
89
|
toast.error('Failed to send reply');
|
|
85
90
|
} finally {
|
|
@@ -162,12 +167,15 @@ useHead({
|
|
|
162
167
|
|
|
163
168
|
<!-- Reply form -->
|
|
164
169
|
<div v-if="isAuthenticated" class="cpub-reply-form">
|
|
170
|
+
<div v-if="replyingTo" class="cpub-replying-to">
|
|
171
|
+
Replying to a comment <button class="cpub-cancel-reply" @click="replyingTo = null"><i class="fa-solid fa-xmark"></i></button>
|
|
172
|
+
</div>
|
|
165
173
|
<div class="cpub-reply-row">
|
|
166
174
|
<input
|
|
167
175
|
v-model="replyContent"
|
|
168
176
|
class="cpub-reply-input"
|
|
169
177
|
type="text"
|
|
170
|
-
placeholder="Write a reply
|
|
178
|
+
placeholder="Write a reply..."
|
|
171
179
|
aria-label="Write a reply"
|
|
172
180
|
@keydown.enter="handleReply"
|
|
173
181
|
/>
|
|
@@ -176,18 +184,53 @@ useHead({
|
|
|
176
184
|
</button>
|
|
177
185
|
</div>
|
|
178
186
|
<p class="cpub-fed-reply-hint">
|
|
179
|
-
<i class="fa-solid fa-globe"></i> Your reply will be sent to {{ hub?.originDomain }} via ActivityPub
|
|
187
|
+
<i class="fa-solid fa-globe"></i> Your reply will also be sent to {{ hub?.originDomain }} via ActivityPub
|
|
180
188
|
</p>
|
|
181
189
|
</div>
|
|
182
190
|
|
|
183
|
-
<!--
|
|
191
|
+
<!-- Replies -->
|
|
184
192
|
<div class="cpub-replies-section">
|
|
185
|
-
<
|
|
186
|
-
|
|
193
|
+
<h3 v-if="replies.length" class="cpub-replies-title">{{ repliesData?.total ?? 0 }} Local Replies</h3>
|
|
194
|
+
<div v-for="reply in replies" :key="reply.id" class="cpub-reply">
|
|
195
|
+
<div class="cpub-reply-author">
|
|
196
|
+
<div class="cpub-reply-avatar">
|
|
197
|
+
<img v-if="reply.author?.avatarUrl" :src="reply.author.avatarUrl" :alt="reply.author?.displayName || reply.author?.username" class="cpub-reply-avatar-img" />
|
|
198
|
+
<span v-else>{{ (reply.author?.displayName || reply.author?.username || 'U').charAt(0).toUpperCase() }}</span>
|
|
199
|
+
</div>
|
|
200
|
+
<NuxtLink v-if="reply.author" :to="`/u/${reply.author.username}`" class="cpub-reply-author-name">{{ reply.author.displayName || reply.author.username }}</NuxtLink>
|
|
201
|
+
<span class="cpub-post-sep">·</span>
|
|
202
|
+
<time class="cpub-post-time">{{ formatDate(reply.createdAt) }}</time>
|
|
203
|
+
</div>
|
|
204
|
+
<div class="cpub-reply-content"><MentionText :text="reply.content" /></div>
|
|
205
|
+
<div class="cpub-reply-actions">
|
|
206
|
+
<button v-if="isAuthenticated" class="cpub-reply-btn" @click="replyingTo = reply.id; replyContent = `@${reply.author?.username ?? ''} `">
|
|
207
|
+
<i class="fa-solid fa-reply"></i> Reply
|
|
208
|
+
</button>
|
|
209
|
+
</div>
|
|
210
|
+
|
|
211
|
+
<!-- Nested replies -->
|
|
212
|
+
<div v-if="reply.replies?.length" class="cpub-nested-replies">
|
|
213
|
+
<div v-for="child in reply.replies" :key="child.id" class="cpub-reply cpub-reply-nested">
|
|
214
|
+
<div class="cpub-reply-author">
|
|
215
|
+
<div class="cpub-reply-avatar">
|
|
216
|
+
<img v-if="child.author?.avatarUrl" :src="child.author.avatarUrl" :alt="child.author?.displayName || child.author?.username" class="cpub-reply-avatar-img" />
|
|
217
|
+
<span v-else>{{ (child.author?.displayName || child.author?.username || 'U').charAt(0).toUpperCase() }}</span>
|
|
218
|
+
</div>
|
|
219
|
+
<NuxtLink v-if="child.author" :to="`/u/${child.author.username}`" class="cpub-reply-author-name">{{ child.author.displayName || child.author.username }}</NuxtLink>
|
|
220
|
+
<span class="cpub-post-sep">·</span>
|
|
221
|
+
<time class="cpub-post-time">{{ formatDate(child.createdAt) }}</time>
|
|
222
|
+
</div>
|
|
223
|
+
<div class="cpub-reply-content"><MentionText :text="child.content" /></div>
|
|
224
|
+
</div>
|
|
225
|
+
</div>
|
|
226
|
+
</div>
|
|
227
|
+
|
|
228
|
+
<!-- Federation thread info -->
|
|
229
|
+
<div class="cpub-fed-thread-info" style="padding: 16px">
|
|
187
230
|
<p class="cpub-empty-state-desc">
|
|
188
|
-
|
|
231
|
+
<i class="fa-solid fa-globe"></i>
|
|
189
232
|
<a v-if="post.objectUri" :href="post.objectUri" target="_blank" rel="noopener noreferrer" class="cpub-inline-link">
|
|
190
|
-
View full thread on
|
|
233
|
+
View full thread on {{ hub?.originDomain }} <i class="fa-solid fa-arrow-up-right-from-square"></i>
|
|
191
234
|
</a>
|
|
192
235
|
</p>
|
|
193
236
|
</div>
|
|
@@ -278,6 +321,11 @@ useHead({
|
|
|
278
321
|
|
|
279
322
|
/* Reply form */
|
|
280
323
|
.cpub-reply-form { margin-bottom: 16px; }
|
|
324
|
+
.cpub-replying-to {
|
|
325
|
+
font-size: 11px; color: var(--text-dim); margin-bottom: 6px;
|
|
326
|
+
display: flex; align-items: center; gap: 6px;
|
|
327
|
+
}
|
|
328
|
+
.cpub-cancel-reply { background: none; border: none; cursor: pointer; color: var(--text-faint); font-size: 12px; }
|
|
281
329
|
.cpub-reply-row { display: flex; gap: 8px; }
|
|
282
330
|
.cpub-reply-input {
|
|
283
331
|
flex: 1; padding: 8px 12px; background: var(--surface); border: var(--border-width-default) solid var(--border);
|
|
@@ -293,8 +341,38 @@ useHead({
|
|
|
293
341
|
|
|
294
342
|
/* Replies section */
|
|
295
343
|
.cpub-replies-section {}
|
|
296
|
-
.cpub-
|
|
297
|
-
|
|
344
|
+
.cpub-replies-title { font-size: 14px; font-weight: 600; margin-bottom: 12px; }
|
|
345
|
+
|
|
346
|
+
.cpub-reply {
|
|
347
|
+
padding: 12px 16px; background: var(--surface); border: var(--border-width-default) solid var(--border);
|
|
348
|
+
margin-bottom: 8px;
|
|
349
|
+
}
|
|
350
|
+
.cpub-reply-nested { margin-left: 24px; border-color: var(--border2); }
|
|
351
|
+
.cpub-nested-replies { margin-top: 8px; }
|
|
352
|
+
|
|
353
|
+
.cpub-reply-author { display: flex; align-items: center; gap: 6px; font-size: 11px; color: var(--text-faint); margin-bottom: 6px; }
|
|
354
|
+
|
|
355
|
+
.cpub-reply-avatar {
|
|
356
|
+
width: 20px; height: 20px; display: flex; align-items: center; justify-content: center;
|
|
357
|
+
background: var(--surface2); border: 1px solid var(--border);
|
|
358
|
+
font-family: var(--font-mono); font-size: 9px; font-weight: 700; color: var(--text-dim);
|
|
359
|
+
overflow: hidden;
|
|
360
|
+
}
|
|
361
|
+
.cpub-reply-avatar-img { width: 100%; height: 100%; object-fit: cover; border-radius: inherit; }
|
|
362
|
+
|
|
363
|
+
.cpub-reply-author-name { font-weight: 500; color: var(--text-dim); text-decoration: none; }
|
|
364
|
+
.cpub-reply-author-name:hover { color: var(--accent); }
|
|
365
|
+
|
|
366
|
+
.cpub-reply-content { font-size: 13px; line-height: 1.6; color: var(--text); }
|
|
367
|
+
|
|
368
|
+
.cpub-reply-actions { margin-top: 6px; }
|
|
369
|
+
.cpub-reply-btn {
|
|
370
|
+
background: none; border: none; cursor: pointer; font-size: 11px;
|
|
371
|
+
color: var(--text-faint); padding: 2px 0;
|
|
372
|
+
}
|
|
373
|
+
.cpub-reply-btn:hover { color: var(--accent); }
|
|
374
|
+
|
|
375
|
+
.cpub-fed-thread-info { text-align: center; margin-top: 8px; }
|
|
298
376
|
.cpub-empty-state-desc { font-size: 12px; color: var(--text-faint); line-height: 1.5; }
|
|
299
377
|
.cpub-inline-link { color: var(--accent); text-decoration: none; white-space: nowrap; }
|
|
300
378
|
.cpub-inline-link:hover { text-decoration: underline; }
|
|
@@ -63,7 +63,7 @@ const postsVM = computed<HubPostViewModel[]>(() => {
|
|
|
63
63
|
author: {
|
|
64
64
|
name: p.author?.displayName || p.author?.username || p.remoteActorName || 'Unknown',
|
|
65
65
|
handle: p.author ? null : remoteDomain(p.remoteActorUri ?? undefined),
|
|
66
|
-
avatarUrl: p.author?.avatarUrl ?? null,
|
|
66
|
+
avatarUrl: p.author?.avatarUrl ?? p.remoteActorAvatarUrl ?? null,
|
|
67
67
|
},
|
|
68
68
|
createdAt: p.createdAt,
|
|
69
69
|
likeCount: p.likeCount ?? 0,
|
|
@@ -127,6 +127,11 @@ function replyDisplayName(reply: { author?: { displayName?: string | null; usern
|
|
|
127
127
|
return reply.remoteActorName || 'Someone';
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
+
function extractDomain(uri: string | null | undefined): string {
|
|
131
|
+
if (!uri) return '';
|
|
132
|
+
try { return new URL(uri).hostname; } catch { return ''; }
|
|
133
|
+
}
|
|
134
|
+
|
|
130
135
|
function formatDate(d: string | Date): string {
|
|
131
136
|
return new Date(d).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric', hour: 'numeric', minute: '2-digit' });
|
|
132
137
|
}
|
|
@@ -176,11 +181,13 @@ useSeoMeta({
|
|
|
176
181
|
<div class="cpub-post-author">
|
|
177
182
|
<div class="cpub-post-avatar">
|
|
178
183
|
<img v-if="post.author?.avatarUrl" :src="post.author.avatarUrl" :alt="post.author?.displayName || post.author?.username" class="cpub-post-avatar-img" />
|
|
184
|
+
<img v-else-if="post.remoteActorAvatarUrl" :src="post.remoteActorAvatarUrl" :alt="post.remoteActorName || 'Remote user'" class="cpub-post-avatar-img" />
|
|
179
185
|
<span v-else>{{ (post.author?.displayName || post.author?.username || post.remoteActorName || 'U').charAt(0).toUpperCase() }}</span>
|
|
180
186
|
</div>
|
|
181
187
|
<NuxtLink v-if="post.author" :to="`/u/${post.author.username}`" class="cpub-post-author-name">{{ post.author.displayName || post.author.username }}</NuxtLink>
|
|
182
188
|
<span v-else class="cpub-post-author-name cpub-reply-remote">
|
|
183
189
|
<i class="fa-solid fa-globe" title="Federated post"></i> {{ post.remoteActorName || 'Someone' }}
|
|
190
|
+
<span v-if="extractDomain(post.remoteActorUri)" class="cpub-remote-domain">@{{ extractDomain(post.remoteActorUri) }}</span>
|
|
184
191
|
</span>
|
|
185
192
|
<span class="cpub-post-sep">·</span>
|
|
186
193
|
<time class="cpub-post-time">{{ formatDate(post.createdAt) }}</time>
|
|
@@ -237,11 +244,13 @@ useSeoMeta({
|
|
|
237
244
|
<div class="cpub-reply-author">
|
|
238
245
|
<div class="cpub-reply-avatar">
|
|
239
246
|
<img v-if="reply.author?.avatarUrl" :src="reply.author.avatarUrl" :alt="reply.author?.displayName || reply.author?.username" class="cpub-reply-avatar-img" />
|
|
247
|
+
<img v-else-if="reply.remoteActorAvatarUrl" :src="reply.remoteActorAvatarUrl" :alt="reply.remoteActorName || 'Remote user'" class="cpub-reply-avatar-img" />
|
|
240
248
|
<span v-else>{{ (replyDisplayName(reply)).charAt(0).toUpperCase() }}</span>
|
|
241
249
|
</div>
|
|
242
250
|
<NuxtLink v-if="reply.author" :to="`/u/${reply.author.username}`" class="cpub-reply-author-name">{{ reply.author.displayName || reply.author.username }}</NuxtLink>
|
|
243
251
|
<span v-else class="cpub-reply-author-name cpub-reply-remote">
|
|
244
252
|
<i class="fa-solid fa-globe" title="Federated reply"></i> {{ reply.remoteActorName || 'Someone' }}
|
|
253
|
+
<span v-if="extractDomain(reply.remoteActorUri)" class="cpub-remote-domain">@{{ extractDomain(reply.remoteActorUri) }}</span>
|
|
245
254
|
</span>
|
|
246
255
|
<span class="cpub-post-sep">·</span>
|
|
247
256
|
<time class="cpub-post-time">{{ formatDate(reply.createdAt) }}</time>
|
|
@@ -259,11 +268,13 @@ useSeoMeta({
|
|
|
259
268
|
<div class="cpub-reply-author">
|
|
260
269
|
<div class="cpub-reply-avatar">
|
|
261
270
|
<img v-if="child.author?.avatarUrl" :src="child.author.avatarUrl" :alt="child.author?.displayName || child.author?.username" class="cpub-reply-avatar-img" />
|
|
271
|
+
<img v-else-if="child.remoteActorAvatarUrl" :src="child.remoteActorAvatarUrl" :alt="child.remoteActorName || 'Remote user'" class="cpub-reply-avatar-img" />
|
|
262
272
|
<span v-else>{{ (replyDisplayName(child)).charAt(0).toUpperCase() }}</span>
|
|
263
273
|
</div>
|
|
264
274
|
<NuxtLink v-if="child.author" :to="`/u/${child.author.username}`" class="cpub-reply-author-name">{{ child.author.displayName || child.author.username }}</NuxtLink>
|
|
265
275
|
<span v-else class="cpub-reply-author-name cpub-reply-remote">
|
|
266
276
|
<i class="fa-solid fa-globe" title="Federated reply"></i> {{ child.remoteActorName || 'Someone' }}
|
|
277
|
+
<span v-if="extractDomain(child.remoteActorUri)" class="cpub-remote-domain">@{{ extractDomain(child.remoteActorUri) }}</span>
|
|
267
278
|
</span>
|
|
268
279
|
<span class="cpub-post-sep">·</span>
|
|
269
280
|
<time class="cpub-post-time">{{ formatDate(child.createdAt) }}</time>
|
|
@@ -419,6 +430,7 @@ useSeoMeta({
|
|
|
419
430
|
.cpub-reply-author-name:hover { color: var(--accent); }
|
|
420
431
|
.cpub-reply-remote { display: inline-flex; align-items: center; gap: 4px; }
|
|
421
432
|
.cpub-reply-remote > i { font-size: 10px; color: var(--accent); }
|
|
433
|
+
.cpub-remote-domain { font-size: 10px; color: var(--text-faint); font-weight: 400; }
|
|
422
434
|
|
|
423
435
|
.cpub-reply-content { font-size: 13px; line-height: 1.6; color: var(--text); }
|
|
424
436
|
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { listFederatedHubPostReplies } from '@commonpub/server';
|
|
2
|
+
|
|
3
|
+
export default defineEventHandler(async (event) => {
|
|
4
|
+
requireFeature('federation');
|
|
5
|
+
const postId = getRouterParam(event, 'postId')!;
|
|
6
|
+
const query = getQuery(event);
|
|
7
|
+
const db = useDB();
|
|
8
|
+
|
|
9
|
+
return listFederatedHubPostReplies(db, postId, {
|
|
10
|
+
limit: query.limit ? Number(query.limit) : undefined,
|
|
11
|
+
offset: query.offset ? Number(query.offset) : undefined,
|
|
12
|
+
});
|
|
13
|
+
});
|
|
@@ -1,17 +1,19 @@
|
|
|
1
|
-
import { sendPostToRemoteHub, getFederatedHubPost, getFederatedHub } from '@commonpub/server';
|
|
1
|
+
import { sendPostToRemoteHub, getFederatedHubPost, getFederatedHub, createFederatedHubPostReply } from '@commonpub/server';
|
|
2
|
+
import type { FederatedHubPostReplyItem } from '@commonpub/server';
|
|
2
3
|
import { z } from 'zod';
|
|
3
4
|
|
|
4
5
|
const replySchema = z.object({
|
|
5
6
|
federatedHubPostId: z.string().uuid(),
|
|
6
7
|
content: z.string().min(1).max(10000),
|
|
8
|
+
parentId: z.string().uuid().optional(),
|
|
7
9
|
});
|
|
8
10
|
|
|
9
|
-
export default defineEventHandler(async (event): Promise<
|
|
11
|
+
export default defineEventHandler(async (event): Promise<FederatedHubPostReplyItem> => {
|
|
10
12
|
requireFeature('federation');
|
|
11
13
|
const user = requireAuth(event);
|
|
12
14
|
const db = useDB();
|
|
13
15
|
const config = useConfig();
|
|
14
|
-
const { federatedHubPostId, content } = await parseBody(event, replySchema);
|
|
16
|
+
const { federatedHubPostId, content, parentId } = await parseBody(event, replySchema);
|
|
15
17
|
|
|
16
18
|
const post = await getFederatedHubPost(db, federatedHubPostId);
|
|
17
19
|
if (!post) {
|
|
@@ -23,7 +25,15 @@ export default defineEventHandler(async (event): Promise<{ success: boolean }> =
|
|
|
23
25
|
throw createError({ statusCode: 404, statusMessage: 'Hub not found' });
|
|
24
26
|
}
|
|
25
27
|
|
|
26
|
-
|
|
28
|
+
// Store locally
|
|
29
|
+
const reply = await createFederatedHubPostReply(db, user.id, {
|
|
30
|
+
federatedHubPostId,
|
|
31
|
+
content,
|
|
32
|
+
parentId,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Send via AP (fire-and-forget — don't block on remote delivery)
|
|
36
|
+
sendPostToRemoteHub(
|
|
27
37
|
db,
|
|
28
38
|
user.id,
|
|
29
39
|
user.username,
|
|
@@ -32,11 +42,7 @@ export default defineEventHandler(async (event): Promise<{ success: boolean }> =
|
|
|
32
42
|
config.instance.domain,
|
|
33
43
|
'text',
|
|
34
44
|
post.objectUri,
|
|
35
|
-
);
|
|
36
|
-
|
|
37
|
-
if (!success) {
|
|
38
|
-
throw createError({ statusCode: 502, statusMessage: 'Could not reach remote hub' });
|
|
39
|
-
}
|
|
45
|
+
).catch(() => { /* best-effort federation delivery */ });
|
|
40
46
|
|
|
41
|
-
return
|
|
47
|
+
return reply;
|
|
42
48
|
});
|