fedipod 1.48.0 → 1.48.1
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 +12 -71
- package/gateway.md +3 -3
- package/gui.md +2 -1
- package/lib/core/place.mjs +10 -0
- package/lib/core/publisher/index.mjs +14 -2
- package/lib/core/store.mjs +6 -2
- package/lib/core/wire.mjs +3 -1
- package/lib/gateway/front-core.mjs +9 -10
- package/lib/gateway/held-mail.mjs +23 -0
- package/lib/gateway/masto-gateway.mjs +50 -5
- package/lib/gateway/quiet.mjs +10 -0
- package/lib/pod/actor.mjs +5 -0
- package/lib/pod/transport.mjs +28 -0
- package/lib/pod/type-index.mjs +23 -0
- package/package.json +1 -1
- package/web/admin/index.html +2 -1
- package/web/app/agent.mjs +29 -4
- package/web/app/dist/boot.js +27 -0
- package/web/app/dist/boot.js.map +2 -2
- package/web/app/dist/sw.js +106 -10
- package/web/app/dist/sw.js.map +3 -3
package/web/app/dist/sw.js
CHANGED
|
@@ -9792,13 +9792,15 @@ function hostMeta(base) {
|
|
|
9792
9792
|
</XRD>
|
|
9793
9793
|
`;
|
|
9794
9794
|
}
|
|
9795
|
-
function jrd({ handle: handle7, host, actor, aliases = [], page: page2 = null }) {
|
|
9795
|
+
function jrd({ handle: handle7, host, actor, aliases = [], page: page2 = null, avatar = null }) {
|
|
9796
9796
|
return {
|
|
9797
9797
|
subject: `acct:${handle7}@${host}`,
|
|
9798
9798
|
...aliases.length ? { aliases } : {},
|
|
9799
9799
|
links: [
|
|
9800
9800
|
{ rel: "self", type: "application/activity+json", href: actor },
|
|
9801
|
-
...page2 ? [{ rel: "http://webfinger.net/rel/profile-page", type: "text/html", href: page2 }] : []
|
|
9801
|
+
...page2 ? [{ rel: "http://webfinger.net/rel/profile-page", type: "text/html", href: page2 }] : [],
|
|
9802
|
+
// The account's picture, as Mastodon's answer names it.
|
|
9803
|
+
...avatar ? [{ rel: "http://webfinger.net/rel/avatar", href: avatar }] : []
|
|
9802
9804
|
]
|
|
9803
9805
|
};
|
|
9804
9806
|
}
|
|
@@ -34462,6 +34464,24 @@ async function register(pod, indexUrl, actorUrl) {
|
|
|
34462
34464
|
if (status2 >= 300) throw new Error(`the type index at ${indexUrl} did not take the registration (${status2})`);
|
|
34463
34465
|
return true;
|
|
34464
34466
|
}
|
|
34467
|
+
async function unregister(pod, indexUrl, actorUrl) {
|
|
34468
|
+
const g = await pod.readRdf(indexUrl);
|
|
34469
|
+
if (!g) return false;
|
|
34470
|
+
const actor = pod.sym(actorUrl);
|
|
34471
|
+
const instance = pod.sym(SOLID + "instance");
|
|
34472
|
+
const gone = [];
|
|
34473
|
+
for (const reg of g.each(null, instance, actor)) {
|
|
34474
|
+
if (!g.holds(reg, pod.sym(SOLID + "forClass"), pod.sym(AS_ACTOR))) continue;
|
|
34475
|
+
const others = g.each(reg, instance, null).filter((i) => i.value !== actorUrl);
|
|
34476
|
+
for (const st2 of others.length ? g.statementsMatching(reg, instance, actor) : g.statementsMatching(reg, null, null)) {
|
|
34477
|
+
gone.push([st2.subject, st2.predicate, st2.object]);
|
|
34478
|
+
}
|
|
34479
|
+
}
|
|
34480
|
+
if (!gone.length) return false;
|
|
34481
|
+
const status2 = await pod.writeRdfChecked(indexUrl, g, { deletes: gone, mustDescribe: indexUrl });
|
|
34482
|
+
if (status2 >= 300) throw new Error(`the type index at ${indexUrl} did not let the registration go (${status2})`);
|
|
34483
|
+
return true;
|
|
34484
|
+
}
|
|
34465
34485
|
async function createPublicIndex(pod, podBase) {
|
|
34466
34486
|
const url = newIndexUrl(podBase);
|
|
34467
34487
|
const doc = pod.sym(url);
|
|
@@ -45398,6 +45418,33 @@ var PodTransport = class {
|
|
|
45398
45418
|
await this.writeAboutWebId(podBase, { inserts: missing, deletes: stale });
|
|
45399
45419
|
return true;
|
|
45400
45420
|
}
|
|
45421
|
+
/**
|
|
45422
|
+
* The account taken out of the WebID profile again, as linkAccountInProfile
|
|
45423
|
+
* wrote it: the foaf:account link, what the profile says of the actor, and
|
|
45424
|
+
* `outbox` when it is the one named. From every document of the profile that
|
|
45425
|
+
* holds any of it, each write checked. Returns how many documents changed.
|
|
45426
|
+
*/
|
|
45427
|
+
async unlinkAccountInProfile({ actorUrl, outbox = null }) {
|
|
45428
|
+
const me = namedNode2(this.webId);
|
|
45429
|
+
const actor = namedNode2(actorUrl);
|
|
45430
|
+
let changed = 0;
|
|
45431
|
+
for (const d of await this.profileDocs(podBaseOfWebId(this.webId))) {
|
|
45432
|
+
if (!d.g) continue;
|
|
45433
|
+
const gone = [
|
|
45434
|
+
...d.g.statementsMatching(me, FOAF("account"), actor),
|
|
45435
|
+
...d.g.statementsMatching(actor, RDF3("type"), FOAF("OnlineAccount")),
|
|
45436
|
+
...d.g.statementsMatching(actor, RDF3("type"), AS("Person")),
|
|
45437
|
+
...d.g.statementsMatching(actor, RDF3("type"), AS("Group")),
|
|
45438
|
+
...d.g.statementsMatching(actor, FOAF("accountName"), null),
|
|
45439
|
+
...outbox ? d.g.statementsMatching(me, AS("outbox"), namedNode2(outbox)) : []
|
|
45440
|
+
].map((st2) => [st2.subject, st2.predicate, st2.object]);
|
|
45441
|
+
if (!gone.length) continue;
|
|
45442
|
+
const status2 = await this.writeRdfChecked(d.url, d.g, { deletes: gone, mustDescribe: d.profile ? this.webId : null });
|
|
45443
|
+
if (status2 >= 300) throw new Error(`not written: ${d.url} \u2192 ${status2}`);
|
|
45444
|
+
changed++;
|
|
45445
|
+
}
|
|
45446
|
+
return changed;
|
|
45447
|
+
}
|
|
45401
45448
|
/**
|
|
45402
45449
|
* An N3 Patch of exactly these statements, or false when the pod will not
|
|
45403
45450
|
* take one and the caller should write the document instead.
|
|
@@ -45611,6 +45658,11 @@ async function recordPlace(pod, podBase, actorAtPod, { create = false } = {}) {
|
|
|
45611
45658
|
}
|
|
45612
45659
|
return await register(pod, index, actorAtPod) ? "registered" : "already";
|
|
45613
45660
|
}
|
|
45661
|
+
async function forgetPlace(pod, podBase, actorAtPod) {
|
|
45662
|
+
const index = await findPublicIndex(pod, podBase);
|
|
45663
|
+
if (!index) return "no-index";
|
|
45664
|
+
return await unregister(pod, index, actorAtPod) ? "removed" : "absent";
|
|
45665
|
+
}
|
|
45614
45666
|
|
|
45615
45667
|
// lib/pod/containers.mjs
|
|
45616
45668
|
var KEEP = { keep: true };
|
|
@@ -45821,9 +45873,13 @@ var PodStore = class {
|
|
|
45821
45873
|
// Read these documents again, whatever is held: a kept copy caught them
|
|
45822
45874
|
// mid-write, so the pod's copy is the one to hold. Throws when one cannot be
|
|
45823
45875
|
// read, so the caller can fall back to a full load rather than act without it.
|
|
45824
|
-
|
|
45876
|
+
// `ifChanged`: a document held here is asked for only if it changed since — a
|
|
45877
|
+
// pod's listing does not change when a document in it is rewritten.
|
|
45878
|
+
async refresh(names, { ifChanged = false } = {}) {
|
|
45825
45879
|
for (const name of names) {
|
|
45826
|
-
const
|
|
45880
|
+
const etag = ifChanged && this.cache.has(name) ? this.etags.get(name) : null;
|
|
45881
|
+
const r = await this.storage.read(name, etag ? { etag } : void 0);
|
|
45882
|
+
if (r.notModified) continue;
|
|
45827
45883
|
if (r.status === 404) {
|
|
45828
45884
|
this.cache.delete(name);
|
|
45829
45885
|
this.lastText.delete(name);
|
|
@@ -46441,6 +46497,9 @@ async function writeProfilePage(pod, urls, html) {
|
|
|
46441
46497
|
function linkInWebIdProfile(pod, { actorUrl, accountName, kind = "person", outbox = null }) {
|
|
46442
46498
|
return pod.linkAccountInProfile({ actorUrl, accountName, kind, outbox });
|
|
46443
46499
|
}
|
|
46500
|
+
async function unlinkInWebIdProfile(pod, { actorUrl, outbox = null }) {
|
|
46501
|
+
return pod.unlinkAccountInProfile({ actorUrl, outbox });
|
|
46502
|
+
}
|
|
46444
46503
|
|
|
46445
46504
|
// lib/pod/inbox.mjs
|
|
46446
46505
|
async function writeKeep(pod, urls) {
|
|
@@ -58595,7 +58654,7 @@ var Publisher = class {
|
|
|
58595
58654
|
await writeWebfinger(
|
|
58596
58655
|
this.remote,
|
|
58597
58656
|
urls,
|
|
58598
|
-
jrd({ handle: this.config.handle, host, actor: urls.actor, page: urls.profileHtml })
|
|
58657
|
+
jrd({ handle: this.config.handle, host, actor: urls.actor, page: urls.profileHtml, avatar: this.config.icon || null })
|
|
58599
58658
|
);
|
|
58600
58659
|
await writeHostMeta(this.remote, urls, hostMeta(urls.base));
|
|
58601
58660
|
const nodeinfoDocUrl = urls.home + "ap/nodeinfo-2.0";
|
|
@@ -58751,6 +58810,17 @@ var Publisher = class {
|
|
|
58751
58810
|
await writeTombstone(this.remote, urls, tombstoneDoc(urls, deletedAt, this.config.kind));
|
|
58752
58811
|
this.store.setConfig({ ...this.store.getConfig(), retiredAt: deletedAt });
|
|
58753
58812
|
await this.store.flush();
|
|
58813
|
+
if (!this.config.forum) {
|
|
58814
|
+
await unlinkInWebIdProfile(this.remote, {
|
|
58815
|
+
actorUrl: urls.actor,
|
|
58816
|
+
outbox: this.clientOrigin ? `${this.clientOrigin}ap/outbox` : this.gatewayOutbox()
|
|
58817
|
+
}).then((n) => {
|
|
58818
|
+
if (n) this.log("the WebID profile no longer lists the actor");
|
|
58819
|
+
}).catch((e) => this.log(`the WebID profile still lists the actor: ${e.message}`));
|
|
58820
|
+
await forgetPlace(this.remote, urls.base, urls.home + "ap/actor").then((r) => {
|
|
58821
|
+
if (r === "removed") this.log("the public type index no longer records the account");
|
|
58822
|
+
}).catch((e) => this.log(`the type index still records the account: ${e.message}`));
|
|
58823
|
+
}
|
|
58754
58824
|
this.log(`retired: Delete sent to ${inboxes.length} inbox(es), actor replaced with a Tombstone`);
|
|
58755
58825
|
return { inboxes: inboxes.length, deletedAt };
|
|
58756
58826
|
}
|
|
@@ -73582,8 +73652,9 @@ var BrowserAgent = class _BrowserAgent {
|
|
|
73582
73652
|
}
|
|
73583
73653
|
// One call to the gateway's owner API, proved with the pod session. What
|
|
73584
73654
|
// comes back on 200 or 410 is the account's standing there, kept for the
|
|
73585
|
-
// manage page; anything else is logged and forgotten.
|
|
73586
|
-
|
|
73655
|
+
// manage page; anything else is logged and forgotten. `standing: false` for
|
|
73656
|
+
// a call whose answer says nothing about the account's standing.
|
|
73657
|
+
async tellGateway(what, body, { standing = true } = {}) {
|
|
73587
73658
|
if (!this.gatewayApi) return null;
|
|
73588
73659
|
let res;
|
|
73589
73660
|
try {
|
|
@@ -73598,12 +73669,16 @@ var BrowserAgent = class _BrowserAgent {
|
|
|
73598
73669
|
}
|
|
73599
73670
|
const json2 = await res.json().catch(() => ({}));
|
|
73600
73671
|
const out = { status: res.status, ...json2 };
|
|
73601
|
-
if (res.status === 200 || res.status === 410)
|
|
73602
|
-
|
|
73672
|
+
if (res.status === 200 || res.status === 410) {
|
|
73673
|
+
if (standing) this.gatewayStanding = out;
|
|
73674
|
+
} else this.log(`gateway ${what}: ${res.status} ${json2.error || ""}`);
|
|
73603
73675
|
return out;
|
|
73604
73676
|
}
|
|
73677
|
+
// The account's picture goes with it, for the address's WebFinger answer;
|
|
73678
|
+
// said only once the account has been read, so "none" is never a guess.
|
|
73605
73679
|
openAtGateway() {
|
|
73606
|
-
|
|
73680
|
+
const config = this.store?.getConfig?.();
|
|
73681
|
+
return this.tellGateway("open", { handle: this.doorKey, ...config ? { icon: config.icon || null } : {} });
|
|
73607
73682
|
}
|
|
73608
73683
|
pauseAtGateway(paused) {
|
|
73609
73684
|
return this.tellGateway("pause", { handle: this.doorKey, paused: !!paused });
|
|
@@ -73612,6 +73687,24 @@ var BrowserAgent = class _BrowserAgent {
|
|
|
73612
73687
|
return this.tellGateway("close", { handle: this.doorKey, confirm: true });
|
|
73613
73688
|
}
|
|
73614
73689
|
static OPEN_EVERY_MS = 60 * 6e4;
|
|
73690
|
+
// A notification made here, from mail read while FediPod is open, goes to
|
|
73691
|
+
// the phones the owner signed up, as one made while it is closed does
|
|
73692
|
+
// (lib/gateway/held-mail.mjs: /api/push). Named a moment later, once written,
|
|
73693
|
+
// so a burst is one call. One from another network is not pushed.
|
|
73694
|
+
static PUSH_AFTER_MS = 2e3;
|
|
73695
|
+
notePush(n) {
|
|
73696
|
+
if (!this.gatewayApi || n?.bsky || n?.via) return;
|
|
73697
|
+
if (!Object.keys(this.store.read("webpush.json", { subs: {} }).subs || {}).length) return;
|
|
73698
|
+
(this._toPush ||= /* @__PURE__ */ new Set()).add(n.id);
|
|
73699
|
+
this._pushTimer ||= setTimeout(async () => {
|
|
73700
|
+
const ids = [...this._toPush];
|
|
73701
|
+
this._toPush.clear();
|
|
73702
|
+
this._pushTimer = null;
|
|
73703
|
+
await this.store.flush?.().catch(() => {
|
|
73704
|
+
});
|
|
73705
|
+
await this.tellGateway("push", { handle: this.doorKey, ids }, { standing: false });
|
|
73706
|
+
}, _BrowserAgent.PUSH_AFTER_MS);
|
|
73707
|
+
}
|
|
73615
73708
|
// The gateway acting for this account while the app is closed, or not. On:
|
|
73616
73709
|
// the rules on the account's folders name the gateway's pod identity, then
|
|
73617
73710
|
// the gateway is told. Off: the gateway is told first, so it stops, then the
|
|
@@ -73727,6 +73820,9 @@ var BrowserAgent = class _BrowserAgent {
|
|
|
73727
73820
|
if (this.viewer) return;
|
|
73728
73821
|
}
|
|
73729
73822
|
}
|
|
73823
|
+
this.store.onEvent = (type, n) => {
|
|
73824
|
+
if (type === "notification") this.notePush(n);
|
|
73825
|
+
};
|
|
73730
73826
|
const now = Date.now();
|
|
73731
73827
|
const hereDue = !warm || now - (warm.hereAt || 0) >= _BrowserAgent.HERE_EVERY_MS;
|
|
73732
73828
|
const here = hereDue ? await this.hereAtGateway() : null;
|