fedipod 1.8.0 → 1.11.0
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/README.md +3 -0
- package/groups.md +1 -0
- package/lib/client/c2s.mjs +19 -5
- package/lib/client/masto/index.mjs +9 -1
- package/lib/client/masto/render.mjs +6 -4
- package/lib/client/masto/statuses.mjs +7 -4
- package/lib/client/oidc-auth.mjs +10 -3
- package/lib/core/as2.mjs +1 -1
- package/lib/core/deliver.mjs +24 -1
- package/lib/core/intake/activities.mjs +54 -0
- package/lib/core/intake/index.mjs +2 -0
- package/lib/core/intake/notes.mjs +7 -0
- package/lib/core/publisher/collections.mjs +7 -4
- package/lib/core/publisher/index.mjs +83 -36
- package/lib/core/publisher/notes.mjs +76 -11
- package/lib/core/publisher/questions.mjs +10 -7
- package/lib/core/social.mjs +3 -0
- package/lib/core/store.mjs +9 -0
- package/lib/core/wire.mjs +96 -13
- package/lib/device/admin/static.mjs +2 -2
- package/lib/device/admin/surface.mjs +11 -2
- package/lib/device/cli/commands/setup.mjs +1 -0
- package/lib/device/setup.mjs +1 -0
- package/lib/gateway/front-core.mjs +33 -5
- package/lib/gateway/httpsig.mjs +7 -3
- package/lib/server/embed.mjs +1 -0
- package/package.json +1 -1
- package/run-agent.mjs +6 -0
- package/scripts/stage-site.mjs +1 -0
- package/web/app/agent.mjs +3 -0
- package/web/app/dist/boot.js +1 -0
- package/web/app/dist/boot.js.map +2 -2
- package/web/app/dist/sw.js +375 -83
- package/web/app/dist/sw.js.map +3 -3
- package/web/app/signup.mjs +1 -0
- package/web/app/site/_redirects +1 -0
- package/web/app/site/boot.js +1 -0
- package/web/app/site/sw.js +375 -83
- package/web/front/admin.js +5 -1
package/web/app/dist/sw.js
CHANGED
|
@@ -9589,6 +9589,7 @@ __export(wire_exports, {
|
|
|
9589
9589
|
AS_CTX: () => AS_CTX,
|
|
9590
9590
|
DEFAULT_ROOT: () => DEFAULT_ROOT,
|
|
9591
9591
|
FOLLOWERS_PAGE_SIZE: () => FOLLOWERS_PAGE_SIZE,
|
|
9592
|
+
HASHTAG_RE: () => HASHTAG_RE,
|
|
9592
9593
|
MENTION_RE: () => MENTION_RE,
|
|
9593
9594
|
OUTBOX_PAGE_SIZE: () => OUTBOX_PAGE_SIZE,
|
|
9594
9595
|
PUBLIC: () => PUBLIC,
|
|
@@ -9615,6 +9616,7 @@ __export(wire_exports, {
|
|
|
9615
9616
|
followersPageId: () => followersPageId,
|
|
9616
9617
|
followersPaging: () => followersPaging,
|
|
9617
9618
|
followsNeedApproval: () => followsNeedApproval,
|
|
9619
|
+
hashtagsIn: () => hashtagsIn,
|
|
9618
9620
|
hostMeta: () => hostMeta,
|
|
9619
9621
|
jrd: () => jrd,
|
|
9620
9622
|
likeActivity: () => likeActivity,
|
|
@@ -9627,11 +9629,13 @@ __export(wire_exports, {
|
|
|
9627
9629
|
orderedCollection: () => orderedCollection,
|
|
9628
9630
|
outboxHead: () => outboxHead,
|
|
9629
9631
|
outboxItemId: () => outboxItemId,
|
|
9632
|
+
outboxLocalItem: () => outboxLocalItem,
|
|
9630
9633
|
outboxPage: () => outboxPage,
|
|
9631
9634
|
outboxPageCount: () => outboxPageCount,
|
|
9632
9635
|
outboxPageId: () => outboxPageId,
|
|
9633
9636
|
outboxPages: () => outboxPages,
|
|
9634
9637
|
outboxPaging: () => outboxPaging,
|
|
9638
|
+
outboxWireItem: () => outboxWireItem,
|
|
9635
9639
|
pageItems: () => pageItems,
|
|
9636
9640
|
profilePageHtml: () => profilePageHtml,
|
|
9637
9641
|
publicHandle: () => publicHandle,
|
|
@@ -9639,6 +9643,7 @@ __export(wire_exports, {
|
|
|
9639
9643
|
rejectActivity: () => rejectActivity,
|
|
9640
9644
|
repliesId: () => repliesId,
|
|
9641
9645
|
sanitizeHtml: () => sanitizeHtml,
|
|
9646
|
+
tagHref: () => tagHref,
|
|
9642
9647
|
titledContent: () => titledContent,
|
|
9643
9648
|
tombstoneDoc: () => tombstoneDoc,
|
|
9644
9649
|
undoActivity: () => undoActivity,
|
|
@@ -9659,11 +9664,14 @@ function hostMeta(base) {
|
|
|
9659
9664
|
</XRD>
|
|
9660
9665
|
`;
|
|
9661
9666
|
}
|
|
9662
|
-
function jrd({ handle: handle7, host, actor, aliases = [] }) {
|
|
9667
|
+
function jrd({ handle: handle7, host, actor, aliases = [], page: page2 = null }) {
|
|
9663
9668
|
return {
|
|
9664
9669
|
subject: `acct:${handle7}@${host}`,
|
|
9665
9670
|
...aliases.length ? { aliases } : {},
|
|
9666
|
-
links: [
|
|
9671
|
+
links: [
|
|
9672
|
+
{ rel: "self", type: "application/activity+json", href: actor },
|
|
9673
|
+
...page2 ? [{ rel: "http://webfinger.net/rel/profile-page", type: "text/html", href: page2 }] : []
|
|
9674
|
+
]
|
|
9667
9675
|
};
|
|
9668
9676
|
}
|
|
9669
9677
|
function followsNeedApproval(config = {}) {
|
|
@@ -9789,9 +9797,30 @@ function actorDoc({
|
|
|
9789
9797
|
publicKey: { id: urls.actor + "#main-key", owner: urls.actor, publicKeyPem }
|
|
9790
9798
|
};
|
|
9791
9799
|
}
|
|
9792
|
-
function profilePageHtml({
|
|
9800
|
+
function profilePageHtml({
|
|
9801
|
+
name,
|
|
9802
|
+
address,
|
|
9803
|
+
summary = null,
|
|
9804
|
+
icon = null,
|
|
9805
|
+
image = null,
|
|
9806
|
+
fields = [],
|
|
9807
|
+
joined = null,
|
|
9808
|
+
pinned = [],
|
|
9809
|
+
kind = "person"
|
|
9810
|
+
}) {
|
|
9793
9811
|
const esc = (s) => String(s ?? "").replace(/[&<>"]/g, (c) => ({ ...HTML_ESCAPES, '"': """ })[c]);
|
|
9794
9812
|
const what = kind === "group" ? "a group" : "an account";
|
|
9813
|
+
const month = (iso) => {
|
|
9814
|
+
const d = new Date(iso);
|
|
9815
|
+
return Number.isNaN(d.getTime()) ? null : d.toLocaleDateString("en", { month: "long", year: "numeric", timeZone: "UTC" });
|
|
9816
|
+
};
|
|
9817
|
+
const day = (iso) => {
|
|
9818
|
+
const d = new Date(iso);
|
|
9819
|
+
return Number.isNaN(d.getTime()) ? "" : d.toISOString().slice(0, 10);
|
|
9820
|
+
};
|
|
9821
|
+
const fieldRows = fields.filter((f) => f?.name && f?.value !== void 0 && f?.value !== null && String(f.value) !== "");
|
|
9822
|
+
const joinedText = joined ? month(joined) : null;
|
|
9823
|
+
const pins = pinned.filter((x) => x && typeof x.content === "string");
|
|
9795
9824
|
return `<!doctype html>
|
|
9796
9825
|
<html lang="en">
|
|
9797
9826
|
<head>
|
|
@@ -9801,14 +9830,23 @@ function profilePageHtml({ name, address, summary = null, icon = null, kind = "p
|
|
|
9801
9830
|
<style>
|
|
9802
9831
|
:root { color-scheme: light dark; }
|
|
9803
9832
|
body { font: 112.5%/1.5 system-ui, sans-serif; max-width: 34rem; margin: 3rem auto; padding: 0 1rem; }
|
|
9804
|
-
img.
|
|
9833
|
+
img.header { width: 100%; max-height: 12rem; object-fit: cover; border-radius: 1rem; }
|
|
9834
|
+
img.avatar { width: 6rem; height: 6rem; border-radius: 1rem; object-fit: cover; margin-top: 1rem; }
|
|
9805
9835
|
h1 { margin: .5rem 0 0; }
|
|
9806
9836
|
.address { font-size: 1.1rem; user-select: all; }
|
|
9837
|
+
dl.fields { display: grid; grid-template-columns: max-content 1fr; gap: .4rem 1rem; margin: 1.5rem 0; }
|
|
9838
|
+
dl.fields dt { font-weight: 600; }
|
|
9839
|
+
dl.fields dd { margin: 0; overflow-wrap: anywhere; }
|
|
9840
|
+
.joined { margin: 1rem 0; }
|
|
9841
|
+
section.pinned h2 { font-size: 1.1rem; margin: 2rem 0 .5rem; }
|
|
9842
|
+
article.pin { border: 1px solid #8884; border-radius: 1rem; padding: 1rem 1.2rem; margin: 1rem 0; }
|
|
9843
|
+
article.pin p:last-child { margin-bottom: 0; }
|
|
9844
|
+
article.pin .when { font-size: 1rem; }
|
|
9807
9845
|
form { margin-top: 2rem; }
|
|
9808
9846
|
label { display: block; margin-bottom: .3rem; }
|
|
9809
9847
|
input { font: inherit; padding: .5rem; width: 14rem; max-width: 100%; }
|
|
9810
9848
|
button { font: inherit; padding: .5rem 1rem; }
|
|
9811
|
-
.hint { color: #
|
|
9849
|
+
.hint { color: #666; font-size: 1rem; }
|
|
9812
9850
|
.err { color: #b00020; }
|
|
9813
9851
|
.err:empty { display: none; }
|
|
9814
9852
|
@media (prefers-color-scheme: dark) {
|
|
@@ -9819,10 +9857,22 @@ button { font: inherit; padding: .5rem 1rem; }
|
|
|
9819
9857
|
</head>
|
|
9820
9858
|
<body>
|
|
9821
9859
|
<main>
|
|
9860
|
+
${image ? `<img class="header" src="${esc(image)}" alt="">` : ""}
|
|
9822
9861
|
${icon ? `<img class="avatar" src="${esc(icon)}" alt="">` : ""}
|
|
9823
9862
|
<h1>${esc(name)}</h1>
|
|
9824
9863
|
<p class="address">${esc(address)}</p>
|
|
9825
9864
|
${summary ? `<div>${summary}</div>` : ""}
|
|
9865
|
+
${fieldRows.length ? `<dl class="fields">
|
|
9866
|
+
${fieldRows.map((f) => `<dt>${esc(f.name)}</dt><dd>${esc(f.value)}</dd>`).join("\n")}
|
|
9867
|
+
</dl>` : ""}
|
|
9868
|
+
${joinedText ? `<p class="joined">Joined ${esc(joinedText)}</p>` : ""}
|
|
9869
|
+
${pins.length ? `<section class="pinned">
|
|
9870
|
+
<h2>Pinned</h2>
|
|
9871
|
+
${pins.map((x) => `<article class="pin">
|
|
9872
|
+
${x.content}
|
|
9873
|
+
<p class="when">${x.url ? `<a href="${esc(x.url)}">${esc(day(x.published))}</a>` : esc(day(x.published))}</p>
|
|
9874
|
+
</article>`).join("\n")}
|
|
9875
|
+
</section>` : ""}
|
|
9826
9876
|
<p>This is ${what} on the Fediverse. To follow it, paste the address above
|
|
9827
9877
|
into the search box of Mastodon or any Fediverse app \u2014 or use the form.</p>
|
|
9828
9878
|
<form id="follow">
|
|
@@ -9926,7 +9976,7 @@ function outboxPage(outboxId, n, items) {
|
|
|
9926
9976
|
id: outboxPageId(outboxId, n),
|
|
9927
9977
|
type: "OrderedCollectionPage",
|
|
9928
9978
|
partOf: outboxId,
|
|
9929
|
-
orderedItems: items,
|
|
9979
|
+
orderedItems: items.map(outboxWireItem),
|
|
9930
9980
|
...n > 1 ? { next: outboxPageId(outboxId, n - 1) } : {}
|
|
9931
9981
|
};
|
|
9932
9982
|
}
|
|
@@ -10046,8 +10096,27 @@ function attachmentsOf(note) {
|
|
|
10046
10096
|
function mentionsIn(text) {
|
|
10047
10097
|
return [...new Set(String(text).match(MENTION_RE) || [])].map((m) => m.slice(1));
|
|
10048
10098
|
}
|
|
10049
|
-
function
|
|
10099
|
+
function hashtagsIn(text) {
|
|
10100
|
+
const seen = /* @__PURE__ */ new Map();
|
|
10101
|
+
for (const m of String(text).matchAll(HASHTAG_RE)) {
|
|
10102
|
+
const key = m[2].toLowerCase();
|
|
10103
|
+
if (!seen.has(key)) seen.set(key, m[2]);
|
|
10104
|
+
}
|
|
10105
|
+
return [...seen.values()];
|
|
10106
|
+
}
|
|
10107
|
+
function tagHref(urls, name) {
|
|
10108
|
+
return urls.profileHtml + "?tag=" + encodeURIComponent(String(name).toLowerCase());
|
|
10109
|
+
}
|
|
10110
|
+
function contentHtml(text, mentions = [], hashtags = []) {
|
|
10050
10111
|
let html = String(text).replace(/[&<>]/g, (c) => HTML_ESCAPES[c]);
|
|
10112
|
+
if (hashtags.length) {
|
|
10113
|
+
const byName = new Map(hashtags.filter((h) => h?.name && h?.href).map((h) => [h.name.toLowerCase(), h.href]));
|
|
10114
|
+
html = html.replace(HASHTAG_RE, (all, lead, name) => {
|
|
10115
|
+
const href = byName.get(name.toLowerCase());
|
|
10116
|
+
if (!href) return all;
|
|
10117
|
+
return `${lead}<a href="${String(href).replace(/[&<>"]/g, (c) => HTML_ESCAPES[c] || """)}" class="mention hashtag" rel="tag">#<span>${name}</span></a>`;
|
|
10118
|
+
});
|
|
10119
|
+
}
|
|
10051
10120
|
for (const m of mentions) {
|
|
10052
10121
|
if (!m?.handle || !m?.actor) continue;
|
|
10053
10122
|
const href = String(m.page || m.actor).replace(/[&<>"]/g, (c) => HTML_ESCAPES[c] || """);
|
|
@@ -10075,28 +10144,35 @@ function noteDoc({
|
|
|
10075
10144
|
mentions = [],
|
|
10076
10145
|
visibility = "public",
|
|
10077
10146
|
summary = null,
|
|
10147
|
+
sensitive = false,
|
|
10078
10148
|
updated = null,
|
|
10079
|
-
container = null
|
|
10149
|
+
container = null,
|
|
10150
|
+
also = []
|
|
10080
10151
|
}) {
|
|
10081
10152
|
const id = (container || urls.notes) + slug;
|
|
10082
10153
|
const who = mentions.map((m) => m.actor);
|
|
10083
|
-
const
|
|
10154
|
+
const extra = visibility === "direct" ? [] : also.filter((a) => a && !who.includes(a));
|
|
10155
|
+
const addressed = addressing(urls, visibility, [...who, ...extra]);
|
|
10156
|
+
const hashtags = hashtagsIn(content).map((name) => ({ name, href: tagHref(urls, name) }));
|
|
10084
10157
|
const note = {
|
|
10085
10158
|
"@context": AS_CTX,
|
|
10086
10159
|
id,
|
|
10087
10160
|
type: "Note",
|
|
10088
10161
|
attributedTo: urls.actor,
|
|
10089
|
-
content: contentHtml(content, mentions),
|
|
10162
|
+
content: contentHtml(content, mentions, hashtags),
|
|
10090
10163
|
published,
|
|
10091
10164
|
to: addressed.to,
|
|
10092
10165
|
cc: addressed.cc,
|
|
10093
10166
|
replies: repliesId(id)
|
|
10094
10167
|
};
|
|
10095
10168
|
if (summary) note.summary = summary;
|
|
10169
|
+
if (summary || sensitive) note.sensitive = true;
|
|
10096
10170
|
if (updated) note.updated = updated;
|
|
10097
|
-
|
|
10098
|
-
|
|
10099
|
-
|
|
10171
|
+
const tag = [
|
|
10172
|
+
...mentions.map((m) => ({ type: "Mention", href: m.actor, name: "@" + m.handle })),
|
|
10173
|
+
...hashtags.map((h) => ({ type: "Hashtag", href: h.href, name: "#" + h.name }))
|
|
10174
|
+
];
|
|
10175
|
+
if (tag.length) note.tag = tag;
|
|
10100
10176
|
if (inReplyTo) note.inReplyTo = inReplyTo;
|
|
10101
10177
|
if (attachments?.length) {
|
|
10102
10178
|
note.attachment = attachments.map((a) => ({
|
|
@@ -10274,7 +10350,7 @@ function addRemoveActivity({ urls, type, object, target, serial }) {
|
|
|
10274
10350
|
target
|
|
10275
10351
|
};
|
|
10276
10352
|
}
|
|
10277
|
-
var import_sanitize_html, AS_CTX, SEC_CTX, PUBLIC, DEFAULT_ROOT, assertionKeyId, OUTBOX_PAGE_SIZE, outboxPageId, outboxPageCount, outboxItemId, FOLLOWERS_PAGE_SIZE, followersPageId, followersPageCount, ALLOWED_TAGS, ALLOWED_ATTRS, MAX_ATTACHMENTS, MAX_ATTACHMENT_URL, attachmentUrl, HTML_ESCAPES, MENTION_RE;
|
|
10353
|
+
var import_sanitize_html, AS_CTX, SEC_CTX, PUBLIC, DEFAULT_ROOT, assertionKeyId, OUTBOX_PAGE_SIZE, outboxPageId, outboxPageCount, outboxItemId, outboxWireItem, outboxLocalItem, FOLLOWERS_PAGE_SIZE, followersPageId, followersPageCount, ALLOWED_TAGS, ALLOWED_ATTRS, MAX_ATTACHMENTS, MAX_ATTACHMENT_URL, attachmentUrl, HTML_ESCAPES, MENTION_RE, HASHTAG_RE;
|
|
10278
10354
|
var init_wire = __esm({
|
|
10279
10355
|
"lib/core/wire.mjs"() {
|
|
10280
10356
|
init_urls();
|
|
@@ -10289,6 +10365,8 @@ var init_wire = __esm({
|
|
|
10289
10365
|
outboxPageId = (outboxId, n) => `${outboxId}-${n}`;
|
|
10290
10366
|
outboxPageCount = (total) => Math.max(1, Math.ceil(total / OUTBOX_PAGE_SIZE));
|
|
10291
10367
|
outboxItemId = (i) => typeof i === "string" ? i : i?.id || null;
|
|
10368
|
+
outboxWireItem = (i) => typeof i === "string" ? createActivityId(i) : i;
|
|
10369
|
+
outboxLocalItem = (i) => typeof i === "string" && i.endsWith("-create") ? i.slice(0, -"-create".length) : i;
|
|
10292
10370
|
FOLLOWERS_PAGE_SIZE = 20;
|
|
10293
10371
|
followersPageId = (id, n) => `${id}-${n}`;
|
|
10294
10372
|
followersPageCount = (total) => Math.max(1, Math.ceil(total / FOLLOWERS_PAGE_SIZE));
|
|
@@ -10329,6 +10407,7 @@ var init_wire = __esm({
|
|
|
10329
10407
|
};
|
|
10330
10408
|
HTML_ESCAPES = { "&": "&", "<": "<", ">": ">" };
|
|
10331
10409
|
MENTION_RE = /@([A-Za-z0-9_.-]+)@([A-Za-z0-9.-]+\.[A-Za-z]{2,}(?::\d+)?)/g;
|
|
10410
|
+
HASHTAG_RE = /(^|[^\/\w)])#([\p{L}\p{N}_]*[\p{L}_][\p{L}\p{N}_]*)/gu;
|
|
10332
10411
|
}
|
|
10333
10412
|
});
|
|
10334
10413
|
|
|
@@ -32650,11 +32729,12 @@ async function verifyHttpSignature(request, { documentLoader, keyCache, timeWind
|
|
|
32650
32729
|
} catch {
|
|
32651
32730
|
result = null;
|
|
32652
32731
|
}
|
|
32732
|
+
const method = request.headers.has("signature-input") ? "rfc9421" : "draft-cavage";
|
|
32653
32733
|
if (result?.verified) {
|
|
32654
32734
|
const key = result.key;
|
|
32655
32735
|
return {
|
|
32656
32736
|
verified: true,
|
|
32657
|
-
method
|
|
32737
|
+
method,
|
|
32658
32738
|
keyId: key.id?.href ?? null,
|
|
32659
32739
|
actor: key.ownerId?.href ?? null,
|
|
32660
32740
|
reason: null,
|
|
@@ -32675,7 +32755,7 @@ async function verifyHttpSignature(request, { documentLoader, keyCache, timeWind
|
|
|
32675
32755
|
if (kind === "keyFetchError") {
|
|
32676
32756
|
return {
|
|
32677
32757
|
verified: false,
|
|
32678
|
-
method
|
|
32758
|
+
method,
|
|
32679
32759
|
keyId: null,
|
|
32680
32760
|
actor: null,
|
|
32681
32761
|
reason: "key-unfetchable",
|
|
@@ -32684,7 +32764,7 @@ async function verifyHttpSignature(request, { documentLoader, keyCache, timeWind
|
|
|
32684
32764
|
}
|
|
32685
32765
|
return {
|
|
32686
32766
|
verified: false,
|
|
32687
|
-
method
|
|
32767
|
+
method,
|
|
32688
32768
|
keyId: null,
|
|
32689
32769
|
actor: null,
|
|
32690
32770
|
reason: "bad-signature",
|
|
@@ -34481,6 +34561,14 @@ var PodStore = class {
|
|
|
34481
34561
|
// — not from the pod, not from the RDF. Unverified entries are dropped first
|
|
34482
34562
|
// now, so a flood can only ever evict itself, and a stranger's genuine
|
|
34483
34563
|
// favourite still shows up, which is what recording them at all is for.
|
|
34564
|
+
// What an Undo unsays: the favourite or boost it names, matched by a
|
|
34565
|
+
// predicate on what was recorded. Returns how many went.
|
|
34566
|
+
removeNotifications(pred) {
|
|
34567
|
+
const all = this.getNotifications();
|
|
34568
|
+
const kept = all.filter((n) => !pred(n));
|
|
34569
|
+
if (kept.length !== all.length) this.write("notifications.json", kept);
|
|
34570
|
+
return all.length - kept.length;
|
|
34571
|
+
}
|
|
34484
34572
|
addNotification(n) {
|
|
34485
34573
|
const all = this.getNotifications();
|
|
34486
34574
|
const id = node_crypto_default.createHash("sha256").update(JSON.stringify(n)).digest("hex").slice(0, 16);
|
|
@@ -45433,8 +45521,9 @@ async function publishOutbox(publisher, outbox, { acls = false, force = false }
|
|
|
45433
45521
|
);
|
|
45434
45522
|
return wrote;
|
|
45435
45523
|
}
|
|
45436
|
-
function readPublishedOutbox(publisher) {
|
|
45437
|
-
|
|
45524
|
+
async function readPublishedOutbox(publisher) {
|
|
45525
|
+
const items = await readPublished(publisher.remote, publisher.urls);
|
|
45526
|
+
return Array.isArray(items) ? items.map(outboxLocalItem) : items;
|
|
45438
45527
|
}
|
|
45439
45528
|
async function publishFollowers(publisher, actors, { acls = false, force = false } = {}) {
|
|
45440
45529
|
const { urls } = publisher;
|
|
@@ -55941,6 +56030,35 @@ async function mentionsFor(publisher, content, inReplyTo) {
|
|
|
55941
56030
|
}
|
|
55942
56031
|
return mentions;
|
|
55943
56032
|
}
|
|
56033
|
+
async function replyTarget(publisher, inReplyTo, mentions = [], visibility = "public") {
|
|
56034
|
+
if (!inReplyTo || visibility === "direct" || !publisher.resolveActor) return null;
|
|
56035
|
+
const parent = publisher.store.getStatuses().find((s) => s.noteId === inReplyTo);
|
|
56036
|
+
let actor = parent?.actor || null;
|
|
56037
|
+
if (!parent) {
|
|
56038
|
+
const doc2 = await publisher.resolveActor(inReplyTo).catch(() => null);
|
|
56039
|
+
const by = doc2?.attributedTo;
|
|
56040
|
+
actor = typeof by === "string" ? by : (Array.isArray(by) ? by[0] : by)?.id || null;
|
|
56041
|
+
if (typeof actor === "object") actor = actor?.id || null;
|
|
56042
|
+
}
|
|
56043
|
+
if (!actor || actor === publisher.urls.actor) return null;
|
|
56044
|
+
if (mentions.some((m) => m.actor === actor)) return null;
|
|
56045
|
+
if (publisher.store.isBlocked?.(actor)) return null;
|
|
56046
|
+
const doc = await publisher.resolveActor(actor).catch(() => null);
|
|
56047
|
+
const inbox = doc?.endpoints?.sharedInbox || doc?.inbox || null;
|
|
56048
|
+
if (!inbox) publisher.log(`reply: no inbox found for ${actor} \u2014 they get it only through followers`);
|
|
56049
|
+
return { actor, inbox };
|
|
56050
|
+
}
|
|
56051
|
+
async function inboxesFor(publisher, actorIds = []) {
|
|
56052
|
+
const out = [];
|
|
56053
|
+
for (const id of [...new Set(actorIds.filter((a) => typeof a === "string" && a && a !== publisher.urls.actor))]) {
|
|
56054
|
+
if (!publisher.resolveActor) break;
|
|
56055
|
+
const doc = await publisher.resolveActor(id).catch(() => null);
|
|
56056
|
+
const inbox = doc?.endpoints?.sharedInbox || doc?.inbox;
|
|
56057
|
+
if (inbox) out.push(inbox);
|
|
56058
|
+
else publisher.log(`addressed actor ${id} has no inbox the agent can find \u2014 not delivered to`);
|
|
56059
|
+
}
|
|
56060
|
+
return out;
|
|
56061
|
+
}
|
|
55944
56062
|
function assertDirectAddressed(content, mentions) {
|
|
55945
56063
|
const wanted = [...new Set(mentionsIn(content))];
|
|
55946
56064
|
const missing = wanted.filter((h) => !mentions.some((m) => m.handle === h));
|
|
@@ -55966,7 +56084,7 @@ function rowContent(obj) {
|
|
|
55966
56084
|
const esc = (v) => String(v).replace(/[&<>"]/gu, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]);
|
|
55967
56085
|
return `<p><a href="${esc(obj?.id || "")}">${esc(obj?.type || "object")}</a></p>`;
|
|
55968
56086
|
}
|
|
55969
|
-
async function publishNote(publisher, content, { inReplyTo, attachments, visibility = "public", spoilerText = null, slug: wanted = null } = {}) {
|
|
56087
|
+
async function publishNote(publisher, content, { inReplyTo, attachments, visibility = "public", spoilerText = null, sensitive = false, slug: wanted = null, also = [], deliverTo = [] } = {}) {
|
|
55970
56088
|
const { urls } = publisher;
|
|
55971
56089
|
const priv = visibility === "private" || visibility === "direct";
|
|
55972
56090
|
if (priv) {
|
|
@@ -55977,6 +56095,7 @@ async function publishNote(publisher, content, { inReplyTo, attachments, visibil
|
|
|
55977
56095
|
const slug = await slugFor(publisher, priv ? urls.privateNotes : urls.notes, wanted, published);
|
|
55978
56096
|
const mentions = await publisher._mentionsFor(content, inReplyTo);
|
|
55979
56097
|
if (visibility === "direct") assertDirectAddressed(content, mentions);
|
|
56098
|
+
const reply = await replyTarget(publisher, inReplyTo, mentions, visibility);
|
|
55980
56099
|
const note = noteDoc({
|
|
55981
56100
|
urls,
|
|
55982
56101
|
slug,
|
|
@@ -55987,8 +56106,11 @@ async function publishNote(publisher, content, { inReplyTo, attachments, visibil
|
|
|
55987
56106
|
mentions,
|
|
55988
56107
|
visibility,
|
|
55989
56108
|
summary: spoilerText,
|
|
55990
|
-
|
|
56109
|
+
sensitive,
|
|
56110
|
+
container: priv ? urls.privateNotes : urls.notes,
|
|
56111
|
+
also: [...also, ...reply ? [reply.actor] : []]
|
|
55991
56112
|
});
|
|
56113
|
+
const addressedInboxes = await inboxesFor(publisher, [...also, ...deliverTo]);
|
|
55992
56114
|
await write4(publisher.remote, note.id, note);
|
|
55993
56115
|
await writeEmptyReplies(
|
|
55994
56116
|
publisher.remote,
|
|
@@ -56007,8 +56129,9 @@ async function publishNote(publisher, content, { inReplyTo, attachments, visibil
|
|
|
56007
56129
|
text: content,
|
|
56008
56130
|
visibility,
|
|
56009
56131
|
...spoilerText ? { spoiler: spoilerText } : {},
|
|
56132
|
+
...note.sensitive ? { sensitive: true } : {},
|
|
56010
56133
|
...attachments?.length ? { attachments } : {},
|
|
56011
|
-
...note
|
|
56134
|
+
...rowTags(note)
|
|
56012
56135
|
});
|
|
56013
56136
|
if (await publisher.store.commit?.() === false) publisher.log(`post published but its timeline row was refused: ${note.id}`);
|
|
56014
56137
|
const create = createActivity(note, urls);
|
|
@@ -56016,7 +56139,9 @@ async function publishNote(publisher, content, { inReplyTo, attachments, visibil
|
|
|
56016
56139
|
const contacts = publisher.store.getContacts();
|
|
56017
56140
|
const inboxes = [...new Set([
|
|
56018
56141
|
...visibility === "direct" ? [] : contacts.followers.map((f) => f.sharedInbox || f.inbox),
|
|
56019
|
-
...mentions.map((m) => m.inbox)
|
|
56142
|
+
...mentions.map((m) => m.inbox),
|
|
56143
|
+
reply?.inbox,
|
|
56144
|
+
...addressedInboxes
|
|
56020
56145
|
].filter(Boolean))];
|
|
56021
56146
|
await publisher.deliverer.deliverToAll(inboxes, create);
|
|
56022
56147
|
publisher.log(`note published: ${note.id} \u2192 ${inboxes.length} inbox(es)`);
|
|
@@ -56035,7 +56160,7 @@ async function publishNote(publisher, content, { inReplyTo, attachments, visibil
|
|
|
56035
56160
|
}
|
|
56036
56161
|
return note;
|
|
56037
56162
|
}
|
|
56038
|
-
async function publishObject(publisher, object, { visibility = "public", slug: wanted = null } = {}) {
|
|
56163
|
+
async function publishObject(publisher, object, { visibility = "public", slug: wanted = null, also = [], deliverTo = [] } = {}) {
|
|
56039
56164
|
const { urls } = publisher;
|
|
56040
56165
|
const priv = visibility === "private" || visibility === "direct";
|
|
56041
56166
|
if (priv) {
|
|
@@ -56047,11 +56172,13 @@ async function publishObject(publisher, object, { visibility = "public", slug: w
|
|
|
56047
56172
|
const pod = publisher.config?.remotePod || urls.base;
|
|
56048
56173
|
const ownId = typeof object.id === "string" && /^https?:\/\//u.test(object.id) && object.id.startsWith(pod) ? object.id : null;
|
|
56049
56174
|
const name = await slugFor(publisher, container, wanted, published);
|
|
56050
|
-
const addressed = addressing(urls, visibility);
|
|
56175
|
+
const addressed = addressing(urls, visibility, visibility === "direct" ? also : []);
|
|
56176
|
+
if (visibility !== "direct") addressed.cc = [.../* @__PURE__ */ new Set([...addressed.cc, ...also])];
|
|
56051
56177
|
let doc = null;
|
|
56052
56178
|
const id = ownId || container + name;
|
|
56053
56179
|
if (!ownId) {
|
|
56054
|
-
|
|
56180
|
+
const { bto, bcc, ...sent } = object;
|
|
56181
|
+
doc = { ...sent, id, attributedTo: urls.actor, published: object.published || published, to: addressed.to, cc: addressed.cc };
|
|
56055
56182
|
if (!doc["@context"]) doc["@context"] = AS_CTX;
|
|
56056
56183
|
if (typeof doc.content === "string") doc.content = sanitizeHtml(doc.content);
|
|
56057
56184
|
await write4(publisher.remote, id, doc);
|
|
@@ -56081,12 +56208,21 @@ async function publishObject(publisher, object, { visibility = "public", slug: w
|
|
|
56081
56208
|
};
|
|
56082
56209
|
await writeCreate(publisher.remote, create.id, create);
|
|
56083
56210
|
const contacts = publisher.store.getContacts();
|
|
56084
|
-
const inboxes =
|
|
56211
|
+
const inboxes = [...new Set([
|
|
56212
|
+
...visibility === "direct" ? [] : contacts.followers.map((f) => f.sharedInbox || f.inbox),
|
|
56213
|
+
...await inboxesFor(publisher, [...also, ...deliverTo])
|
|
56214
|
+
].filter(Boolean))];
|
|
56085
56215
|
await publisher.deliverer.deliverToAll(inboxes, create);
|
|
56086
56216
|
publisher.log(`${row.type || "object"} published: ${id} \u2192 ${inboxes.length} inbox(es)`);
|
|
56087
56217
|
return { id, createId, copied: !ownId };
|
|
56088
56218
|
}
|
|
56089
|
-
|
|
56219
|
+
function rowTags(note) {
|
|
56220
|
+
const tags = [].concat(note.tag || []);
|
|
56221
|
+
const mentions = tags.filter((t) => t.type === "Mention").map((t) => ({ href: t.href, name: t.name }));
|
|
56222
|
+
const hashtags = tags.filter((t) => t.type === "Hashtag").map((t) => ({ name: String(t.name).replace(/^#/, ""), url: t.href }));
|
|
56223
|
+
return { ...mentions.length ? { mentions } : {}, ...hashtags.length ? { tags: hashtags } : {} };
|
|
56224
|
+
}
|
|
56225
|
+
async function updateNote(publisher, s, { content, spoilerText = null, sensitive = null, attachments = null } = {}) {
|
|
56090
56226
|
const { urls } = publisher;
|
|
56091
56227
|
const updated = (/* @__PURE__ */ new Date()).toISOString();
|
|
56092
56228
|
const inText = new Set(mentionsIn(content));
|
|
@@ -56103,6 +56239,7 @@ async function updateNote(publisher, s, { content, spoilerText = null, attachmen
|
|
|
56103
56239
|
const atts = attachments ?? s.attachments ?? [];
|
|
56104
56240
|
const container = String(s.noteId).startsWith(urls.privateNotes) ? urls.privateNotes : urls.notes;
|
|
56105
56241
|
const slug = s.slug || String(s.noteId).slice(container.length);
|
|
56242
|
+
const reply = await replyTarget(publisher, s.inReplyTo, mentions, s.visibility || "public");
|
|
56106
56243
|
const note = noteDoc({
|
|
56107
56244
|
urls,
|
|
56108
56245
|
slug,
|
|
@@ -56113,8 +56250,10 @@ async function updateNote(publisher, s, { content, spoilerText = null, attachmen
|
|
|
56113
56250
|
mentions,
|
|
56114
56251
|
visibility: s.visibility || "public",
|
|
56115
56252
|
summary: spoilerText,
|
|
56253
|
+
sensitive: sensitive ?? !!s.sensitive,
|
|
56116
56254
|
updated,
|
|
56117
|
-
container
|
|
56255
|
+
container,
|
|
56256
|
+
also: reply ? [reply.actor] : []
|
|
56118
56257
|
});
|
|
56119
56258
|
await write4(publisher.remote, note.id, note);
|
|
56120
56259
|
await writeCreate(publisher.remote, createActivityId(note.id), createActivity(note, urls));
|
|
@@ -56123,14 +56262,17 @@ async function updateNote(publisher, s, { content, spoilerText = null, attachmen
|
|
|
56123
56262
|
text: content,
|
|
56124
56263
|
editedAt: updated,
|
|
56125
56264
|
spoiler: spoilerText || void 0,
|
|
56265
|
+
sensitive: note.sensitive || void 0,
|
|
56126
56266
|
attachments: atts.length ? atts : void 0,
|
|
56127
|
-
mentions: note
|
|
56267
|
+
mentions: rowTags(note).mentions,
|
|
56268
|
+
tags: rowTags(note).tags
|
|
56128
56269
|
});
|
|
56129
56270
|
const update = updateActivity(note, urls);
|
|
56130
56271
|
const contacts = publisher.store.getContacts();
|
|
56131
56272
|
const inboxes = [...new Set([
|
|
56132
56273
|
...s.visibility === "direct" ? [] : contacts.followers.map((f) => f.sharedInbox || f.inbox),
|
|
56133
|
-
...mentions.map((m) => m.inbox)
|
|
56274
|
+
...mentions.map((m) => m.inbox),
|
|
56275
|
+
reply?.inbox
|
|
56134
56276
|
].filter(Boolean))];
|
|
56135
56277
|
await publisher.deliverer.deliverToAll(inboxes, update);
|
|
56136
56278
|
publisher.log(`note edited: ${note.id} \u2192 ${inboxes.length} inbox(es)`);
|
|
@@ -56356,7 +56498,8 @@ async function publishQuestion(publisher, content, {
|
|
|
56356
56498
|
expiresAt = null,
|
|
56357
56499
|
inReplyTo = void 0,
|
|
56358
56500
|
visibility = "public",
|
|
56359
|
-
spoilerText = null
|
|
56501
|
+
spoilerText = null,
|
|
56502
|
+
sensitive = false
|
|
56360
56503
|
} = {}) {
|
|
56361
56504
|
const { urls } = publisher;
|
|
56362
56505
|
const priv = visibility === "private" || visibility === "direct";
|
|
@@ -56383,6 +56526,7 @@ async function publishQuestion(publisher, content, {
|
|
|
56383
56526
|
const slug = published.slice(0, 10) + "-" + node_crypto_default.randomBytes(4).toString("hex");
|
|
56384
56527
|
const mentions = await publisher._mentionsFor(content, inReplyTo);
|
|
56385
56528
|
if (visibility === "direct") assertDirectAddressed(content, mentions);
|
|
56529
|
+
const reply = await replyTarget(publisher, inReplyTo, mentions, visibility);
|
|
56386
56530
|
const poll = {
|
|
56387
56531
|
multiple: !!multiple,
|
|
56388
56532
|
expiresAt: expiresAt || null,
|
|
@@ -56391,7 +56535,7 @@ async function publishQuestion(publisher, content, {
|
|
|
56391
56535
|
votersCount: 0,
|
|
56392
56536
|
// Resolved once, here: a tally rewrite must not cost a webfinger lookup
|
|
56393
56537
|
// per vote for people the poll named.
|
|
56394
|
-
mentionInboxes: [...new Set(mentions.map((m) => m.inbox).filter(Boolean))]
|
|
56538
|
+
mentionInboxes: [...new Set([...mentions.map((m) => m.inbox), reply?.inbox].filter(Boolean))]
|
|
56395
56539
|
};
|
|
56396
56540
|
const question = questionDoc({
|
|
56397
56541
|
urls,
|
|
@@ -56403,6 +56547,8 @@ async function publishQuestion(publisher, content, {
|
|
|
56403
56547
|
mentions,
|
|
56404
56548
|
visibility,
|
|
56405
56549
|
summary: spoilerText,
|
|
56550
|
+
sensitive,
|
|
56551
|
+
also: reply ? [reply.actor] : [],
|
|
56406
56552
|
container: priv ? urls.privateNotes : urls.notes,
|
|
56407
56553
|
options: poll.options,
|
|
56408
56554
|
multiple: poll.multiple,
|
|
@@ -56427,8 +56573,10 @@ async function publishQuestion(publisher, content, {
|
|
|
56427
56573
|
visibility,
|
|
56428
56574
|
poll,
|
|
56429
56575
|
inReplyTo,
|
|
56576
|
+
...reply ? { replyActor: reply.actor } : {},
|
|
56430
56577
|
...spoilerText ? { spoiler: spoilerText } : {},
|
|
56431
|
-
...question.
|
|
56578
|
+
...question.sensitive ? { sensitive: true } : {},
|
|
56579
|
+
...rowTags(question)
|
|
56432
56580
|
});
|
|
56433
56581
|
if (await publisher.store.commit?.() === false) publisher.log(`poll published but its timeline row was refused: ${question.id}`);
|
|
56434
56582
|
const create = publisher._pollActivity("Create", question, createActivityId(question.id));
|
|
@@ -56507,8 +56655,10 @@ async function republishPoll(publisher, questionId, { closing = null } = {}) {
|
|
|
56507
56655
|
inReplyTo: s.inReplyTo,
|
|
56508
56656
|
attachments: [],
|
|
56509
56657
|
mentions,
|
|
56658
|
+
also: s.replyActor ? [s.replyActor] : [],
|
|
56510
56659
|
visibility: s.visibility || "public",
|
|
56511
56660
|
summary: s.spoiler || null,
|
|
56661
|
+
sensitive: !!s.sensitive,
|
|
56512
56662
|
container,
|
|
56513
56663
|
options: poll.options,
|
|
56514
56664
|
multiple: !!poll.multiple,
|
|
@@ -56580,6 +56730,7 @@ var Publisher = class {
|
|
|
56580
56730
|
log: log2 = console.log,
|
|
56581
56731
|
probeFetch = null,
|
|
56582
56732
|
resolveMention = null,
|
|
56733
|
+
resolveActor = null,
|
|
56583
56734
|
clientOrigin = null
|
|
56584
56735
|
}) {
|
|
56585
56736
|
this.config = config;
|
|
@@ -56594,6 +56745,7 @@ var Publisher = class {
|
|
|
56594
56745
|
if (this.urls.toPod && this.remote?.setUrlMap) this.remote.setUrlMap(this.urls.toPod);
|
|
56595
56746
|
this.probeFetch = probeFetch || ((u, i) => this.remote.probe(u, i));
|
|
56596
56747
|
this.resolveMention = resolveMention;
|
|
56748
|
+
this.resolveActor = resolveActor;
|
|
56597
56749
|
this.pollTimers = /* @__PURE__ */ new Map();
|
|
56598
56750
|
this.log = log2;
|
|
56599
56751
|
}
|
|
@@ -56619,19 +56771,17 @@ var Publisher = class {
|
|
|
56619
56771
|
if (gw.frontActor) return String(gw.frontActor).replace(/ap\/actor\/?$/u, "ap/outbox");
|
|
56620
56772
|
return String(gw.url).replace(/ap\/inbox\/?$/u, "ap/outbox");
|
|
56621
56773
|
}
|
|
56622
|
-
|
|
56774
|
+
// Every actor document this identity publishes, from one place: the
|
|
56775
|
+
// profile publish and a Move both write it, and a Move that rebuilt it
|
|
56776
|
+
// by hand dropped the gateway inbox, the outbox door and the collections.
|
|
56777
|
+
actorDocFor({ priv = false, moderators = null, inbox = null, movedTo = this.config.movedTo || null } = {}) {
|
|
56623
56778
|
const { urls } = this;
|
|
56624
|
-
|
|
56625
|
-
const priv = await this.privateReady() === true;
|
|
56626
|
-
const moderators = (this.config.moderators || []).length ? urls.moderators : null;
|
|
56627
|
-
const gw = this.config.gateway;
|
|
56628
|
-
const gwActive = gw && gw.url && gw.mode && gw.mode !== "off";
|
|
56629
|
-
const actorDoc2 = actorDoc({
|
|
56779
|
+
return actorDoc({
|
|
56630
56780
|
urls,
|
|
56631
56781
|
handle: publicHandle(this.config),
|
|
56632
56782
|
name: this.config.name,
|
|
56633
56783
|
publicKeyPem: this.publicKeyPem,
|
|
56634
|
-
movedTo
|
|
56784
|
+
movedTo,
|
|
56635
56785
|
kind: this.config.kind,
|
|
56636
56786
|
approveJoins: followsNeedApproval(this.config),
|
|
56637
56787
|
assertionKey: this.assertionKey,
|
|
@@ -56645,7 +56795,7 @@ var Publisher = class {
|
|
|
56645
56795
|
pendingFollowers: priv ? urls.pendingFollowers : null,
|
|
56646
56796
|
pendingFollowing: priv ? urls.pendingFollowing : null,
|
|
56647
56797
|
blocked: priv ? urls.blocked : null,
|
|
56648
|
-
inbox
|
|
56798
|
+
inbox,
|
|
56649
56799
|
// The agent's own outbox endpoint, where it is reachable: a client
|
|
56650
56800
|
// following the actor must arrive somewhere that will take a write.
|
|
56651
56801
|
// Otherwise the Gateway's outbox door, when one is attached.
|
|
@@ -56655,6 +56805,49 @@ var Publisher = class {
|
|
|
56655
56805
|
oauthAuthorize: this.clientOrigin ? `${this.clientOrigin}oauth/authorize` : null,
|
|
56656
56806
|
oauthToken: this.clientOrigin ? `${this.clientOrigin}oauth/token` : null
|
|
56657
56807
|
});
|
|
56808
|
+
}
|
|
56809
|
+
// The human half: a page a browser can open and follow from. The actor
|
|
56810
|
+
// document is for servers; this is the address you hand to a person. It
|
|
56811
|
+
// shows who the account is, its fields, when it joined and what it pinned;
|
|
56812
|
+
// the counts and the posts live in the collections. Written only when its
|
|
56813
|
+
// content changed.
|
|
56814
|
+
async publishProfilePage({ force = false } = {}) {
|
|
56815
|
+
const { urls } = this;
|
|
56816
|
+
const host = new URL(urls.base).host;
|
|
56817
|
+
if (!this.config.createdAt) {
|
|
56818
|
+
const oldest = this.store.getStatuses().filter((s) => s.kind === "post" && s.published).map((s) => s.published).sort()[0];
|
|
56819
|
+
this.config.createdAt = oldest || (/* @__PURE__ */ new Date()).toISOString();
|
|
56820
|
+
this.store.setConfig?.({ ...this.store.getConfig?.(), createdAt: this.config.createdAt });
|
|
56821
|
+
}
|
|
56822
|
+
const pinned = this.store.getStatuses().filter((s) => s.kind === "post" && s.pinned && s.visibility !== "private" && s.visibility !== "direct").sort((a, b) => String(b.published).localeCompare(String(a.published))).slice(0, 5).map((s) => ({ content: s.content, published: s.published, url: s.noteId }));
|
|
56823
|
+
const frontActor = this.config.gateway?.frontActor;
|
|
56824
|
+
const address = frontActor ? `@${publicHandle(this.config)}@${new URL(frontActor).host}` : webfingerHost(urls.base) ? `@${this.config.handle}@${host}` : urls.actor;
|
|
56825
|
+
const html = profilePageHtml({
|
|
56826
|
+
name: this.config.name || this.config.handle,
|
|
56827
|
+
address,
|
|
56828
|
+
summary: this.config.summary ? contentHtml(this.config.summary) : null,
|
|
56829
|
+
icon: this.config.icon || null,
|
|
56830
|
+
image: this.config.image || null,
|
|
56831
|
+
fields: this.config.fields || [],
|
|
56832
|
+
joined: this.config.createdAt,
|
|
56833
|
+
pinned,
|
|
56834
|
+
kind: this.config.kind
|
|
56835
|
+
});
|
|
56836
|
+
const digest = node_crypto_default.createHash("sha256").update(html).digest("hex").slice(0, 32);
|
|
56837
|
+
const seen = this.store.read("published.json", {});
|
|
56838
|
+
if (!force && seen.pageDigest === digest) return false;
|
|
56839
|
+
await writeProfilePage(this.remote, urls, html);
|
|
56840
|
+
this.store.write("published.json", { ...this.store.read("published.json", {}), pageDigest: digest });
|
|
56841
|
+
return true;
|
|
56842
|
+
}
|
|
56843
|
+
async publishProfile({ force = false } = {}) {
|
|
56844
|
+
const { urls } = this;
|
|
56845
|
+
const host = new URL(urls.base).host;
|
|
56846
|
+
const priv = await this.privateReady() === true;
|
|
56847
|
+
const moderators = (this.config.moderators || []).length ? urls.moderators : null;
|
|
56848
|
+
const gw = this.config.gateway;
|
|
56849
|
+
const gwActive = gw && gw.url && gw.mode && gw.mode !== "off";
|
|
56850
|
+
const actorDoc2 = this.actorDocFor({ priv, moderators, inbox: gwActive ? gw.url : null });
|
|
56658
56851
|
const surface = node_crypto_default.createHash("sha256").update(JSON.stringify({
|
|
56659
56852
|
actor: actorDoc2,
|
|
56660
56853
|
handle: this.config.handle,
|
|
@@ -56663,6 +56856,7 @@ var Publisher = class {
|
|
|
56663
56856
|
version: AGENT_VERSION,
|
|
56664
56857
|
moderators: this.config.moderators || []
|
|
56665
56858
|
})).digest("hex").slice(0, 32);
|
|
56859
|
+
await this.publishProfilePage({ force });
|
|
56666
56860
|
if (!force && this.store.read("published.json", {}).surfaceDigest === surface) {
|
|
56667
56861
|
this.log("profile unchanged \u2014 nothing republished");
|
|
56668
56862
|
return { unreachable: [], updated: 0, skipped: true };
|
|
@@ -56670,7 +56864,7 @@ var Publisher = class {
|
|
|
56670
56864
|
await writeWebfinger(
|
|
56671
56865
|
this.remote,
|
|
56672
56866
|
urls,
|
|
56673
|
-
jrd({ handle: this.config.handle, host, actor: urls.actor })
|
|
56867
|
+
jrd({ handle: this.config.handle, host, actor: urls.actor, page: urls.profileHtml })
|
|
56674
56868
|
);
|
|
56675
56869
|
await writeHostMeta(this.remote, urls, hostMeta(urls.base));
|
|
56676
56870
|
const nodeinfoDocUrl = urls.home + "ap/nodeinfo-2.0";
|
|
@@ -56689,13 +56883,6 @@ var Publisher = class {
|
|
|
56689
56883
|
);
|
|
56690
56884
|
}
|
|
56691
56885
|
if (gwActive) await this.publishGatewayPolicy();
|
|
56692
|
-
await writeProfilePage(this.remote, urls, profilePageHtml({
|
|
56693
|
-
name: this.config.name || this.config.handle,
|
|
56694
|
-
address: webfingerHost(urls.base) ? `@${this.config.handle}@${host}` : urls.actor,
|
|
56695
|
-
summary: this.config.summary ? contentHtml(this.config.summary) : null,
|
|
56696
|
-
icon: this.config.icon || null,
|
|
56697
|
-
kind: this.config.kind
|
|
56698
|
-
}));
|
|
56699
56886
|
try {
|
|
56700
56887
|
const wrote = await linkInWebIdProfile(this.remote, {
|
|
56701
56888
|
actorUrl: urls.actor,
|
|
@@ -56865,21 +57052,15 @@ var Publisher = class {
|
|
|
56865
57052
|
await this.deliverer.deliverToAll(inboxes, moveActivity(urls, target, Date.parse(at)));
|
|
56866
57053
|
this.config.movedTo = target;
|
|
56867
57054
|
this.store.setConfig({ ...this.store.getConfig(), movedTo: target, movedAt: at });
|
|
56868
|
-
|
|
56869
|
-
|
|
56870
|
-
|
|
56871
|
-
|
|
56872
|
-
|
|
56873
|
-
|
|
56874
|
-
|
|
56875
|
-
|
|
56876
|
-
|
|
56877
|
-
summary: this.config.summary || null,
|
|
56878
|
-
icon: this.config.icon || null,
|
|
56879
|
-
image: this.config.image || null,
|
|
56880
|
-
fields: this.config.fields || [],
|
|
56881
|
-
webId: this.remote.webId || null,
|
|
56882
|
-
aliases: this.config.aliases || []
|
|
57055
|
+
const gw = this.config.gateway;
|
|
57056
|
+
const gwActive = gw && gw.url && gw.mode && gw.mode !== "off";
|
|
57057
|
+
const priv = await this.privateReady() === true;
|
|
57058
|
+
const moderators = (this.config.moderators || []).length ? urls.moderators : null;
|
|
57059
|
+
await writeMoved(this.remote, urls, this.actorDocFor({
|
|
57060
|
+
priv,
|
|
57061
|
+
moderators,
|
|
57062
|
+
inbox: gwActive ? gw.url : null,
|
|
57063
|
+
movedTo: target
|
|
56883
57064
|
}));
|
|
56884
57065
|
await this.store.flush();
|
|
56885
57066
|
this.log(`moved to ${target}: Move sent to ${inboxes.length} inbox(es), actor now advertises movedTo`);
|
|
@@ -57583,6 +57764,8 @@ async function onFollow(intake, activity, actor, { trusted = false } = {}) {
|
|
|
57583
57764
|
intake.log(`Accept sent \u2192 ${doc.inbox}`);
|
|
57584
57765
|
}
|
|
57585
57766
|
async function onUndo(intake, activity, actor, { trusted = false } = {}) {
|
|
57767
|
+
const inner = activity.object && typeof activity.object === "object" ? activity.object : null;
|
|
57768
|
+
if (inner?.type === "Like" || inner?.type === "Announce") return onUndoReaction(intake, inner, actor, { trusted });
|
|
57586
57769
|
if (typeof activity.object === "object" && activity.object?.type && activity.object.type !== "Follow") return;
|
|
57587
57770
|
const named = typeof activity.object === "string" ? activity.object : activity.object?.id;
|
|
57588
57771
|
if (!named && !trusted) return;
|
|
@@ -57670,6 +57853,47 @@ async function onAnnounce(intake, activity, actor, objectId) {
|
|
|
57670
57853
|
}
|
|
57671
57854
|
return intake.ingestNote(objectId, actor, { via: actor });
|
|
57672
57855
|
}
|
|
57856
|
+
async function onUndoReaction(intake, inner, actor, { trusted = false } = {}) {
|
|
57857
|
+
const innerActor = typeof inner.actor === "string" ? inner.actor : inner.actor?.id;
|
|
57858
|
+
if (innerActor && innerActor !== actor) return;
|
|
57859
|
+
const wrapped = inner.object;
|
|
57860
|
+
const target = wrapped && typeof wrapped === "object" && (wrapped.type === "Create" || wrapped.type === "Update") ? wrapped.object : wrapped;
|
|
57861
|
+
const objectId = typeof target === "string" ? target : target?.id;
|
|
57862
|
+
if (!objectId) return;
|
|
57863
|
+
if (objectId.startsWith(intake.urls.notes)) {
|
|
57864
|
+
const type = inner.type === "Like" ? "favourite" : "reblog";
|
|
57865
|
+
const gone = intake.store.removeNotifications((n) => n.type === type && n.actor === actor && n.noteId === objectId);
|
|
57866
|
+
if (gone) intake.log(`${inner.type} withdrawn by ${actor} on ${objectId}`);
|
|
57867
|
+
return;
|
|
57868
|
+
}
|
|
57869
|
+
if (inner.type !== "Announce") return;
|
|
57870
|
+
const followed = trusted || intake.store.getContacts().following.some((f) => f.actor === actor && f.accepted);
|
|
57871
|
+
if (!followed) return;
|
|
57872
|
+
const s = intake.store.getStatuses().find((x) => x.noteId === objectId);
|
|
57873
|
+
if (!s || s.kind === "post" || s.via !== actor) return;
|
|
57874
|
+
await intake.forget(s);
|
|
57875
|
+
intake.log(`boost withdrawn by ${actor}: ${objectId} left the timeline`);
|
|
57876
|
+
}
|
|
57877
|
+
async function onBlock(intake, activity, actor, { trusted = false } = {}) {
|
|
57878
|
+
const objectId = typeof activity.object === "string" ? activity.object : activity.object?.id;
|
|
57879
|
+
if (objectId !== intake.urls.actor) return;
|
|
57880
|
+
if (!trusted) {
|
|
57881
|
+
intake.log(`Block from ${actor} arrived unverified \u2014 ignored`);
|
|
57882
|
+
return;
|
|
57883
|
+
}
|
|
57884
|
+
const contacts = intake.store.getContacts();
|
|
57885
|
+
const wasFollower = contacts.followers.some((f) => f.actor === actor);
|
|
57886
|
+
const wasFollowing = contacts.following.some((f) => f.actor === actor);
|
|
57887
|
+
if (wasFollower) dropFollower(contacts, actor, "blocked-us");
|
|
57888
|
+
contacts.following = contacts.following.filter((f) => f.actor !== actor);
|
|
57889
|
+
intake.store.setContacts(contacts);
|
|
57890
|
+
const reqs = intake.store.getRequests();
|
|
57891
|
+
const left = reqs.filter((r) => r.actor !== actor);
|
|
57892
|
+
if (left.length !== reqs.length) intake.store.setRequests(left);
|
|
57893
|
+
await intake.republish({ followers: wasFollower, following: wasFollowing, pending: true });
|
|
57894
|
+
const what = [wasFollower && "their follow of us", wasFollowing && "our follow of them"].filter(Boolean).join(" and ");
|
|
57895
|
+
intake.log(`blocked by ${actor}: ${what || "no relationship"} dropped`);
|
|
57896
|
+
}
|
|
57673
57897
|
async function onDelete(intake, activity, actor) {
|
|
57674
57898
|
const objectId = typeof activity.object === "string" ? activity.object : activity.object?.id;
|
|
57675
57899
|
if (!objectId) return "Delete without object id";
|
|
@@ -57875,6 +58099,7 @@ async function ingestNote(intake, objectId, actor, { via, inline = null } = {})
|
|
|
57875
58099
|
const followed = via || known2 || !known2 && await intake.isCoMember(author);
|
|
57876
58100
|
const kind = followed ? "timeline" : "mention";
|
|
57877
58101
|
const mentions = [].concat(note.tag || []).filter((t) => t?.type === "Mention" && t.href && t.name).slice(0, MAX_MENTIONS).map((t) => ({ href: httpOnly(String(t.href).slice(0, MAX_URL_CHARS)), name: String(t.name).slice(0, 256) })).filter((m) => m.href);
|
|
58102
|
+
const hashtags = [].concat(note.tag || []).filter((t) => t?.type === "Hashtag" && t.name).slice(0, MAX_MENTIONS).map((t) => ({ name: String(t.name).replace(/^#/, "").slice(0, 128), url: httpOnly(String(t.href || "").slice(0, MAX_URL_CHARS)) })).filter((t) => t.name);
|
|
57878
58103
|
const emojis = emojisOf(note);
|
|
57879
58104
|
const poll = pollOf(note);
|
|
57880
58105
|
const audience = [].concat(note.to || [], note.cc || []).map(String);
|
|
@@ -57891,6 +58116,8 @@ async function ingestNote(intake, objectId, actor, { via, inline = null } = {})
|
|
|
57891
58116
|
...nonPublic ? { nonPublic: true } : {},
|
|
57892
58117
|
// The author's content warning, shown as one: plain text only.
|
|
57893
58118
|
...note.summary ? { spoiler: String(note.summary).replace(/<[^>]*>/g, "") } : {},
|
|
58119
|
+
...note.sensitive === true ? { sensitive: true } : {},
|
|
58120
|
+
...hashtags.length ? { tags: hashtags } : {},
|
|
57894
58121
|
...poll ? { poll } : {},
|
|
57895
58122
|
...emojis.length ? { emojis } : {},
|
|
57896
58123
|
...mentions.length ? { mentions } : {},
|
|
@@ -63413,6 +63640,8 @@ var Intake = class {
|
|
|
63413
63640
|
case "Add":
|
|
63414
63641
|
case "Remove":
|
|
63415
63642
|
return this.onAddRemove(activity, actor);
|
|
63643
|
+
case "Block":
|
|
63644
|
+
return this.onBlock(activity, actor, { trusted });
|
|
63416
63645
|
default:
|
|
63417
63646
|
this.log(`ignored ${activity.type} from ${actor}`);
|
|
63418
63647
|
}
|
|
@@ -63506,6 +63735,9 @@ var Intake = class {
|
|
|
63506
63735
|
onUndo(...a) {
|
|
63507
63736
|
return onUndo(this, ...a);
|
|
63508
63737
|
}
|
|
63738
|
+
onBlock(...a) {
|
|
63739
|
+
return onBlock(this, ...a);
|
|
63740
|
+
}
|
|
63509
63741
|
onCreate(...a) {
|
|
63510
63742
|
return onCreate(this, ...a);
|
|
63511
63743
|
}
|
|
@@ -63986,6 +64218,9 @@ async function pinStatus(agent2, s, pinned) {
|
|
|
63986
64218
|
if (s.actor !== agent2.publisher.urls.actor) throw new Error("not your status");
|
|
63987
64219
|
const updated = agent2.store.updateStatus(s.noteId, { pinned: !!pinned });
|
|
63988
64220
|
await agent2.publisher.publishFeatured().catch((e) => agent2.log?.(`featured: ${e.message}`));
|
|
64221
|
+
if (agent2.publisher.publishProfilePage) {
|
|
64222
|
+
await agent2.publisher.publishProfilePage().catch((e) => agent2.log?.(`profile page: ${e.message}`));
|
|
64223
|
+
}
|
|
63989
64224
|
agent2.publisher.publishProfile?.().catch(() => {
|
|
63990
64225
|
});
|
|
63991
64226
|
return updated || s;
|
|
@@ -64135,7 +64370,7 @@ var C2S = class {
|
|
|
64135
64370
|
return this.send(res, 405, { error: "deliveries go to this actor's inbox on the pod, which the actor document names; this address is the owner reading their own" });
|
|
64136
64371
|
}
|
|
64137
64372
|
const reader = await this.auth(req, pathname);
|
|
64138
|
-
if (!reader.ok) return this.send(res, reader.status, { error: reader.error });
|
|
64373
|
+
if (!reader.ok) return this.send(res, reader.status, { error: reader.error }, reader.headers || {});
|
|
64139
64374
|
return this.sendInbox(res, url);
|
|
64140
64375
|
}
|
|
64141
64376
|
if (req.method === "GET" || req.method === "HEAD") {
|
|
@@ -64148,7 +64383,7 @@ var C2S = class {
|
|
|
64148
64383
|
return this.send(res, 405, { error: "POST the outbox; GET redirects to the pod" });
|
|
64149
64384
|
}
|
|
64150
64385
|
const who = await this.auth(req, pathname);
|
|
64151
|
-
if (!who.ok) return this.send(res, who.status, { error: who.error });
|
|
64386
|
+
if (!who.ok) return this.send(res, who.status, { error: who.error }, who.headers || {});
|
|
64152
64387
|
if (this.agent.viewer) {
|
|
64153
64388
|
const took = await this.agent.requestTakeover?.();
|
|
64154
64389
|
if (!took) return this.send(res, 503, { error: "another agent is active for this pod \u2014 takeover failed, try again" });
|
|
@@ -64202,6 +64437,13 @@ var C2S = class {
|
|
|
64202
64437
|
return reply(422, { error: e.message || String(e) });
|
|
64203
64438
|
}
|
|
64204
64439
|
}
|
|
64440
|
+
// The actors a client addressed by id, beyond the audience words: the ones
|
|
64441
|
+
// in to/cc are listed and delivered to; the ones in bto/bcc are delivered
|
|
64442
|
+
// to and never listed (§6).
|
|
64443
|
+
addressedActors(activity, object) {
|
|
64444
|
+
const pick = (...fields) => [...new Set(fields.flatMap((f) => arr(activity[f] ?? object?.[f]).map(idOf)))].filter((a) => typeof a === "string" && /^https?:\/\//u.test(a) && a !== PUBLIC && a !== this.urls.followers && a !== this.urls.actor);
|
|
64445
|
+
return { also: pick("to", "cc"), deliverTo: pick("bto", "bcc") };
|
|
64446
|
+
}
|
|
64205
64447
|
async _dispatch(activity, { slug, raw, reply }) {
|
|
64206
64448
|
const agent2 = this.agent;
|
|
64207
64449
|
const object = typeof activity.object === "object" && activity.object !== null ? activity.object : null;
|
|
@@ -64211,9 +64453,10 @@ var C2S = class {
|
|
|
64211
64453
|
const makes = object?.type || "Note";
|
|
64212
64454
|
if (!object) return reply(422, { error: "a Create carries the object it creates" });
|
|
64213
64455
|
const visibility = this.visibilityOf(activity, object);
|
|
64456
|
+
const { also, deliverTo } = this.addressedActors(activity, object);
|
|
64214
64457
|
if (makes !== "Note" && makes !== "Question") {
|
|
64215
64458
|
const asSent = raw && typeof raw === "object" && !Array.isArray(raw) ? ACTIVITY_TYPES.has(raw.type) ? raw.object && typeof raw.object === "object" ? raw.object : null : raw : null;
|
|
64216
|
-
const made = await agent2.publisher.publishObject(asSent || object, { visibility, slug });
|
|
64459
|
+
const made = await agent2.publisher.publishObject(asSent || object, { visibility, slug, also, deliverTo });
|
|
64217
64460
|
return reply(201, { id: made.createId, object: made.id }, { location: made.createId });
|
|
64218
64461
|
}
|
|
64219
64462
|
const text = String(object.source?.content ?? object.content ?? "");
|
|
@@ -64229,7 +64472,8 @@ var C2S = class {
|
|
|
64229
64472
|
expiresAt: object.endTime || null,
|
|
64230
64473
|
inReplyTo: idOf(object.inReplyTo) || void 0,
|
|
64231
64474
|
visibility,
|
|
64232
|
-
spoilerText: object.summary || null
|
|
64475
|
+
spoilerText: object.summary || null,
|
|
64476
|
+
sensitive: object.sensitive === true
|
|
64233
64477
|
});
|
|
64234
64478
|
return reply(
|
|
64235
64479
|
201,
|
|
@@ -64250,7 +64494,10 @@ var C2S = class {
|
|
|
64250
64494
|
attachments,
|
|
64251
64495
|
visibility,
|
|
64252
64496
|
spoilerText: object.summary || null,
|
|
64253
|
-
|
|
64497
|
+
sensitive: object.sensitive === true,
|
|
64498
|
+
slug,
|
|
64499
|
+
also,
|
|
64500
|
+
deliverTo
|
|
64254
64501
|
});
|
|
64255
64502
|
return reply(
|
|
64256
64503
|
201,
|
|
@@ -64277,6 +64524,7 @@ var C2S = class {
|
|
|
64277
64524
|
await agent2.publisher.updateNote(s, {
|
|
64278
64525
|
content: text,
|
|
64279
64526
|
spoilerText: object?.summary || null,
|
|
64527
|
+
sensitive: object?.sensitive === void 0 ? null : object.sensitive === true,
|
|
64280
64528
|
attachments
|
|
64281
64529
|
});
|
|
64282
64530
|
return reply(200, { ok: true, object: s.noteId });
|
|
@@ -65441,7 +65689,7 @@ function account(api, actorUrl, { selfAcct } = {}) {
|
|
|
65441
65689
|
bot: false,
|
|
65442
65690
|
discoverable: true,
|
|
65443
65691
|
group: self2 ? api.store.getConfig()?.kind === "group" : cached.type === "Group",
|
|
65444
|
-
created_at: "2026-01-01T00:00:00.000Z",
|
|
65692
|
+
created_at: self2 && api.store.getConfig()?.createdAt || "2026-01-01T00:00:00.000Z",
|
|
65445
65693
|
note: (self2 ? api.store.getConfig()?.summary : cached.summary) || "",
|
|
65446
65694
|
url: actorUrl,
|
|
65447
65695
|
uri: actorUrl,
|
|
@@ -65482,7 +65730,7 @@ function page(api, items, url, { limit = 20, max = 40, idOf: idOf2 = (s) => api.
|
|
|
65482
65730
|
}
|
|
65483
65731
|
const pageItems2 = items.slice(0, n);
|
|
65484
65732
|
if (!pageItems2.length) return { items: pageItems2, headers: {} };
|
|
65485
|
-
const base =
|
|
65733
|
+
const base = url.origin + url.pathname;
|
|
65486
65734
|
const q = (extra) => {
|
|
65487
65735
|
const u = new URL(base);
|
|
65488
65736
|
for (const [k, v] of url.searchParams) if (!["max_id", "since_id", "min_id"].includes(k)) u.searchParams.set(k, v);
|
|
@@ -65608,7 +65856,7 @@ function status(api, s, { all } = {}) {
|
|
|
65608
65856
|
created_at: s.published || (/* @__PURE__ */ new Date()).toISOString(),
|
|
65609
65857
|
in_reply_to_id: s.inReplyTo ? api.store.idFor(s.inReplyTo) : null,
|
|
65610
65858
|
in_reply_to_account_id: null,
|
|
65611
|
-
sensitive: !!s.spoiler,
|
|
65859
|
+
sensitive: !!(s.spoiler || s.sensitive),
|
|
65612
65860
|
spoiler_text: s.spoiler || "",
|
|
65613
65861
|
visibility: s.visibility || "public",
|
|
65614
65862
|
language: null,
|
|
@@ -65648,7 +65896,7 @@ function status(api, s, { all } = {}) {
|
|
|
65648
65896
|
acct: bare.includes("@") ? bare : host ? `${user}@${host}` : bare
|
|
65649
65897
|
};
|
|
65650
65898
|
}),
|
|
65651
|
-
tags: [],
|
|
65899
|
+
tags: (s.tags || []).map((t) => ({ name: t.name, url: t.url })),
|
|
65652
65900
|
emojis: (s.emojis || []).map((e) => ({
|
|
65653
65901
|
shortcode: e.shortcode,
|
|
65654
65902
|
url: e.url,
|
|
@@ -66580,6 +66828,7 @@ async function handle6(api, ctx) {
|
|
|
66580
66828
|
if (ready !== true) return send(422, { error: ready });
|
|
66581
66829
|
}
|
|
66582
66830
|
const spoilerText = String(body.spoiler_text || "").trim() || null;
|
|
66831
|
+
const sensitive = body.sensitive === true || body.sensitive === "true" || body.sensitive === "1";
|
|
66583
66832
|
const mediaIds = [].concat(body.media_ids || body["media_ids[]"] || []).filter(Boolean);
|
|
66584
66833
|
const media = api.store.getMedia();
|
|
66585
66834
|
const attachments = mediaIds.map((id) => media[id] && { id, ...media[id] }).filter(Boolean);
|
|
@@ -66610,7 +66859,8 @@ async function handle6(api, ctx) {
|
|
|
66610
66859
|
expiresAt: new Date(Date.now() + seconds * 1e3).toISOString(),
|
|
66611
66860
|
inReplyTo,
|
|
66612
66861
|
visibility,
|
|
66613
|
-
spoilerText
|
|
66862
|
+
spoilerText,
|
|
66863
|
+
sensitive
|
|
66614
66864
|
});
|
|
66615
66865
|
return send(200, api.status(api.store.getStatuses().find((x) => x.noteId === q.id)));
|
|
66616
66866
|
} catch (e) {
|
|
@@ -66639,7 +66889,7 @@ async function handle6(api, ctx) {
|
|
|
66639
66889
|
try {
|
|
66640
66890
|
note = await api.agent.publisher.publishNote(
|
|
66641
66891
|
body.status,
|
|
66642
|
-
{ inReplyTo, attachments, visibility, spoilerText }
|
|
66892
|
+
{ inReplyTo, attachments, visibility, spoilerText, sensitive }
|
|
66643
66893
|
);
|
|
66644
66894
|
} catch (e) {
|
|
66645
66895
|
if (e.code === "unaddressed") return send(422, { error: e.message });
|
|
@@ -66663,7 +66913,7 @@ async function handle6(api, ctx) {
|
|
|
66663
66913
|
return send(200, [{
|
|
66664
66914
|
content: s.content || "",
|
|
66665
66915
|
spoiler_text: s.spoiler || "",
|
|
66666
|
-
sensitive: !!s.spoiler,
|
|
66916
|
+
sensitive: !!(s.spoiler || s.sensitive),
|
|
66667
66917
|
created_at: s.editedAt || s.published,
|
|
66668
66918
|
account: api.account(s.actor),
|
|
66669
66919
|
media_attachments: (s.attachments || []).map((a) => api.mediaJson(a)),
|
|
@@ -66688,9 +66938,10 @@ async function handle6(api, ctx) {
|
|
|
66688
66938
|
const media = api.store.getMedia();
|
|
66689
66939
|
const attachments = mediaIds.length ? mediaIds.map((id) => media[id] && { id, ...media[id] }).filter(Boolean) : null;
|
|
66690
66940
|
const spoilerText = String(body.spoiler_text || "").trim() || null;
|
|
66941
|
+
const sensitive = body.sensitive === void 0 ? null : body.sensitive === true || body.sensitive === "true" || body.sensitive === "1";
|
|
66691
66942
|
const patched = await api.agent.publisher.updateNote(
|
|
66692
66943
|
s,
|
|
66693
|
-
{ content: body.status, spoilerText, attachments }
|
|
66944
|
+
{ content: body.status, spoilerText, sensitive, attachments }
|
|
66694
66945
|
);
|
|
66695
66946
|
return send(200, api.status(patched || s));
|
|
66696
66947
|
}
|
|
@@ -66929,7 +67180,13 @@ var MastoApi = class _MastoApi {
|
|
|
66929
67180
|
res.end(body);
|
|
66930
67181
|
return true;
|
|
66931
67182
|
};
|
|
66932
|
-
const
|
|
67183
|
+
const scheme = String(this.scheme || (req.headers?.["x-forwarded-proto"] === "https" || req.socket?.encrypted ? "https" : "http")).replace(/:.*$/, "");
|
|
67184
|
+
let self2 = url;
|
|
67185
|
+
try {
|
|
67186
|
+
self2 = new URL(`${scheme}://${req.headers?.host || "localhost"}${this.mount || ""}${pathname}${url.search || ""}`);
|
|
67187
|
+
} catch {
|
|
67188
|
+
}
|
|
67189
|
+
const ctx = { req, res, pathname, url: self2, send };
|
|
66933
67190
|
if (await handle(this, ctx)) return true;
|
|
66934
67191
|
if (!pathname.startsWith("/api/")) return false;
|
|
66935
67192
|
if (await handle2(this, ctx)) return true;
|
|
@@ -67940,6 +68197,7 @@ var hostOf = (inbox) => {
|
|
|
67940
68197
|
}
|
|
67941
68198
|
};
|
|
67942
68199
|
var aboutTheHost = (e) => !e.status || e.status >= 500 || e.status === 429;
|
|
68200
|
+
var unsalvageable = (e) => e.status === 404 || e.status === 410;
|
|
67943
68201
|
var Deliverer = class {
|
|
67944
68202
|
// passive: signing-only (viewer-mode agents) — no queue drain timer, so a
|
|
67945
68203
|
// read-only agent never mutates shared delivery state. startQueue() flips
|
|
@@ -67952,9 +68210,11 @@ var Deliverer = class {
|
|
|
67952
68210
|
edPrivate = null,
|
|
67953
68211
|
proofKeyId = null,
|
|
67954
68212
|
log: log2 = console.log,
|
|
67955
|
-
passive = false
|
|
68213
|
+
passive = false,
|
|
68214
|
+
onGone = null
|
|
67956
68215
|
}) {
|
|
67957
68216
|
this.store = store;
|
|
68217
|
+
this.onGone = onGone;
|
|
67958
68218
|
this.keyId = keyId;
|
|
67959
68219
|
this.rsaPrivate = rsaPrivate;
|
|
67960
68220
|
this.actorId = actorId;
|
|
@@ -68050,6 +68310,10 @@ var Deliverer = class {
|
|
|
68050
68310
|
await this.deliverNow(inbox, signed);
|
|
68051
68311
|
this.log(`delivered ${signed.type} \u2192 ${inbox}`);
|
|
68052
68312
|
} catch (e) {
|
|
68313
|
+
if (unsalvageable(e)) {
|
|
68314
|
+
await this._unsalvageable(inbox, e);
|
|
68315
|
+
return;
|
|
68316
|
+
}
|
|
68053
68317
|
this.log(`delivery failed (${e.message}) \u2014 queued`);
|
|
68054
68318
|
const wait = e.retryAfterMs || 6e4;
|
|
68055
68319
|
if (aboutTheHost(e)) {
|
|
@@ -68059,6 +68323,26 @@ var Deliverer = class {
|
|
|
68059
68323
|
this._enqueue({ inbox, activity: signed, attempts: 1, nextAt: Date.now() + wait });
|
|
68060
68324
|
}
|
|
68061
68325
|
}
|
|
68326
|
+
// Not retried, and on a 410 the followers who received there are dropped,
|
|
68327
|
+
// so the next fan-out stops asking; the followers collection is republished
|
|
68328
|
+
// through onGone. A 404 drops only this delivery.
|
|
68329
|
+
async _unsalvageable(inbox, e) {
|
|
68330
|
+
if (e.status !== 410) {
|
|
68331
|
+
this.log(`delivery dropped (${e.message}) \u2014 nothing answers there`);
|
|
68332
|
+
return;
|
|
68333
|
+
}
|
|
68334
|
+
const contacts = this.store.getContacts?.();
|
|
68335
|
+
const left = (contacts?.followers || []).filter((f) => f.inbox === inbox || f.sharedInbox === inbox);
|
|
68336
|
+
this.log(`${inbox} is gone (410) \u2014 delivery dropped${left.length ? `, ${left.length} follower(s) there forgotten` : ""}`);
|
|
68337
|
+
if (!left.length) return;
|
|
68338
|
+
for (const f of left) dropFollower(contacts, f.actor, "inbox-gone");
|
|
68339
|
+
this.store.setContacts(contacts);
|
|
68340
|
+
try {
|
|
68341
|
+
await this.onGone?.();
|
|
68342
|
+
} catch (err) {
|
|
68343
|
+
this.log(`followers republish after 410: ${err.message}`);
|
|
68344
|
+
}
|
|
68345
|
+
}
|
|
68062
68346
|
// The queue has a ceiling: a fan-out into a sea of down servers must not grow
|
|
68063
68347
|
// queue.json without bound. Overflow keeps the evidence as a dead letter.
|
|
68064
68348
|
_enqueue(item) {
|
|
@@ -68143,6 +68427,11 @@ var Deliverer = class {
|
|
|
68143
68427
|
changed = true;
|
|
68144
68428
|
this.log(`retry ok: ${item.activity.type} \u2192 ${item.inbox}`);
|
|
68145
68429
|
} catch (e) {
|
|
68430
|
+
if (unsalvageable(e)) {
|
|
68431
|
+
changed = true;
|
|
68432
|
+
await this._unsalvageable(item.inbox, e);
|
|
68433
|
+
continue;
|
|
68434
|
+
}
|
|
68146
68435
|
item.attempts += 1;
|
|
68147
68436
|
changed = true;
|
|
68148
68437
|
const wait = e.retryAfterMs || ladderFor(item);
|
|
@@ -70325,6 +70614,7 @@ var BrowserAgent = class _BrowserAgent {
|
|
|
70325
70614
|
try {
|
|
70326
70615
|
await this.store.load({ force: true }).catch((e) => this.log(`re-reading state: ${e.message}`));
|
|
70327
70616
|
await this.publisher.healStatuses().catch((e) => this.log(`healing the timeline index: ${e.message}`));
|
|
70617
|
+
await this.publisher.publishProfilePage().catch((e) => this.log(`profile page: ${e.message}`));
|
|
70328
70618
|
this.deliverer?.startQueue?.();
|
|
70329
70619
|
await this.publisher.publishProfile();
|
|
70330
70620
|
await this.store.flush?.();
|
|
@@ -70436,7 +70726,8 @@ var BrowserAgent = class _BrowserAgent {
|
|
|
70436
70726
|
// The relay finds the account by the front's own key for it, which for a
|
|
70437
70727
|
// mail-door account is the full address, not the bare handle.
|
|
70438
70728
|
handle: doorKeyOf(config.gateway?.url) || config.handle,
|
|
70439
|
-
sessionFetch: session.fetch
|
|
70729
|
+
sessionFetch: session.fetch,
|
|
70730
|
+
onGone: () => this.publisher.publishCollections({ followers: true })
|
|
70440
70731
|
});
|
|
70441
70732
|
this.publisher = new Publisher({
|
|
70442
70733
|
config: this.store.getConfig(),
|
|
@@ -70449,7 +70740,8 @@ var BrowserAgent = class _BrowserAgent {
|
|
|
70449
70740
|
// Who a post names, resolved — the same lookup the DeviceAgent
|
|
70450
70741
|
// gives its publisher. Without it no mention from the browser ever
|
|
70451
70742
|
// resolved: a direct message went to nobody, a mention notified no one.
|
|
70452
|
-
resolveMention: (h) => resolveHandle(this, h)
|
|
70743
|
+
resolveMention: (h) => resolveHandle(this, h),
|
|
70744
|
+
resolveActor: (u) => this.intake.fetchAP(u)
|
|
70453
70745
|
});
|
|
70454
70746
|
this.atproto = new BrowserAtproto({ store: this.store, actorId: this.urls.actor, log: this.log });
|
|
70455
70747
|
this.publisher.atproto = this.atproto;
|