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.
@@ -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
- async refresh(names) {
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 r = await this.storage.read(name);
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
- async tellGateway(what, body) {
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) this.gatewayStanding = out;
73602
- else this.log(`gateway ${what}: ${res.status} ${json2.error || ""}`);
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
- return this.tellGateway("open", { handle: this.doorKey });
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;