fedipod 1.30.0 → 1.36.6

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.
Files changed (43) hide show
  1. package/README.md +1 -1
  2. package/gateway.md +37 -0
  3. package/gui.md +2 -2
  4. package/lib/connections/bskyfeed.mjs +6 -0
  5. package/lib/core/deliver.mjs +52 -22
  6. package/lib/core/intake/index.mjs +17 -4
  7. package/lib/core/lease.mjs +7 -7
  8. package/lib/core/store.mjs +25 -5
  9. package/lib/gateway/caches.mjs +36 -0
  10. package/lib/gateway/front-core.mjs +59 -66
  11. package/lib/gateway/gateway-core.mjs +20 -8
  12. package/lib/gateway/headers.mjs +49 -0
  13. package/lib/gateway/notices.mjs +84 -0
  14. package/lib/gateway/quiet.mjs +189 -0
  15. package/lib/pod/containers.mjs +5 -1
  16. package/lib/pod/inbox.mjs +22 -3
  17. package/lib/pod/transport.mjs +36 -3
  18. package/lib/session/README.md +14 -0
  19. package/lib/session/fedi-account.mjs +62 -0
  20. package/lib/session/package.json +10 -2
  21. package/package.json +1 -1
  22. package/run-agent.mjs +16 -0
  23. package/scripts/stage-site.mjs +6 -3
  24. package/web/admin/bar.css +44 -10
  25. package/web/admin/client/index.html +29 -2
  26. package/web/admin/gateway.js +19 -0
  27. package/web/admin/index.html +46 -3
  28. package/web/admin/notices-bar.js +90 -0
  29. package/web/admin/record.js +1 -0
  30. package/web/admin/setup/index.html +29 -2
  31. package/web/admin/upkeep.js +5 -1
  32. package/web/app/admin-facade.mjs +24 -0
  33. package/web/app/agent.mjs +59 -6
  34. package/web/app/boot.mjs +2 -0
  35. package/web/app/deliver-relay.mjs +47 -7
  36. package/web/app/dist/boot.js +39 -4
  37. package/web/app/dist/boot.js.map +2 -2
  38. package/web/app/dist/sw.js +273 -38
  39. package/web/app/dist/sw.js.map +3 -3
  40. package/web/app/update.js +4 -0
  41. package/web/front/admin.html +3 -1
  42. package/web/front/notices.html +69 -0
  43. package/web/front/notices.js +107 -0
@@ -32822,7 +32822,29 @@ var PodTransport = class {
32822
32822
  const podTarget = this.toPod ? this.toPod(targetUrl) : targetUrl;
32823
32823
  const url = await this.aclUrlFor(podTarget);
32824
32824
  if (!await this.aclWritable(url)) return null;
32825
- return this.put(url, this.aclDoc(podTarget, publicModes, { ...opts, aclUrl: url }), "text/turtle");
32825
+ const doc = this.aclDoc(podTarget, publicModes, { ...opts, aclUrl: url });
32826
+ if (opts.ifChanged && await this.aclSame(url, doc)) return { status: 304, unchanged: true };
32827
+ return this.put(url, doc, "text/turtle");
32828
+ }
32829
+ // Whether the pod's rule at `aclUrl` states exactly what `doc` states.
32830
+ // Compared as graphs, not bytes: the pod serialises what it holds its own
32831
+ // way. Every rule this file writes names its subjects, so triple sets are
32832
+ // enough; anything unreadable or with blank nodes reads as different.
32833
+ async aclSame(aclUrl, doc) {
32834
+ try {
32835
+ const res = await this.fetch(aclUrl, { headers: { accept: "text/turtle" } });
32836
+ if (res.status !== 200) return false;
32837
+ const triples = (text) => {
32838
+ const g = graph();
32839
+ parse2(text, g, aclUrl, "text/turtle");
32840
+ if (g.statements.some((st2) => st2.subject.termType === "BlankNode" || st2.object.termType === "BlankNode")) return null;
32841
+ return g.statements.map((st2) => `${st2.subject.value} ${st2.predicate.value} ${st2.object.value}`).sort().join("\n");
32842
+ };
32843
+ const theirs = triples(await res.text());
32844
+ return theirs !== null && theirs === triples(doc);
32845
+ } catch {
32846
+ return false;
32847
+ }
32826
32848
  }
32827
32849
  // Child documents of an LDP container (URLs under it, excluding aux docs).
32828
32850
  // Revalidated: the inbox is polled every couple of minutes and is usually
@@ -32846,10 +32868,15 @@ var PodTransport = class {
32846
32868
  parse2(body, g, url, "text/turtle");
32847
32869
  const here = namedNode2(url);
32848
32870
  const seen = /* @__PURE__ */ new Set();
32871
+ const receipts = /* @__PURE__ */ new Set();
32849
32872
  const list = [];
32850
32873
  for (const child of g.each(here, LDP("contains"), null, here)) {
32851
32874
  const u = child.value;
32852
- if (!u.startsWith(url) || u === url || /\.(acl|meta|receipt\.json)$/.test(u) || seen.has(u)) continue;
32875
+ if (u.endsWith(".receipt.json")) {
32876
+ receipts.add(u);
32877
+ continue;
32878
+ }
32879
+ if (!u.startsWith(url) || u === url || /\.(acl|meta)$/.test(u) || seen.has(u)) continue;
32853
32880
  seen.add(u);
32854
32881
  list.push({
32855
32882
  url: u,
@@ -32857,10 +32884,16 @@ var PodTransport = class {
32857
32884
  modified: g.any(child, DC("modified"), null, here)?.value || null
32858
32885
  });
32859
32886
  }
32887
+ for (const item of list) item.receipt = receipts.has(item.url + ".receipt.json");
32888
+ const orphans = [...receipts].filter((r) => !seen.has(r.slice(0, -".receipt.json".length)));
32860
32889
  list.sort((a, b) => String(a.modified || "").localeCompare(String(b.modified || "")));
32861
- this._listCache.set(url, { etag: res.headers.get("etag"), children: list });
32890
+ this._listCache.set(url, { etag: res.headers.get("etag"), children: list, orphans });
32862
32891
  return list;
32863
32892
  }
32893
+ /** Receipts in the last listing of `url` whose item is gone. */
32894
+ orphanReceipts(url) {
32895
+ return this._listCache?.get(url)?.orphans ?? [];
32896
+ }
32864
32897
  /**
32865
32898
  * The WebID profile advertises the actor as an account:
32866
32899
  * <webId> foaf:account <actor> .
@@ -33662,6 +33695,7 @@ async function podForFrontedAddress(handle) {
33662
33695
  `/.well-known/webfinger?resource=${encodeURIComponent(`acct:${handle}@${location.host}`)}`,
33663
33696
  { headers: { accept: "application/jrd+json, application/json" } }
33664
33697
  ).catch(() => null);
33698
+ if (res?.status === 410) throw new Error(`@${handle}@${location.host} is closed: nothing on the pod behind it was touched, but the address is gone for good.`);
33665
33699
  if (!res || res.status >= 400) throw new Error(`nobody at this site is called @${handle}@${location.host}`);
33666
33700
  const doc = await res.json().catch(() => ({}));
33667
33701
  const podActorId = (doc.aliases || []).find((a) => /\/ap\/actor$/u.test(String(a)));
@@ -33809,7 +33843,8 @@ if (typeof document !== "undefined") (async () => {
33809
33843
  // login and the identity screen lands here.
33810
33844
  "no-account-here": { title: "No FediPod account in that pod", retry: "Create one on this pod", go: () => showIdentity() },
33811
33845
  "no-account": { title: "No FediPod account in that pod", retry: "Create one on this pod", go: () => showIdentity() },
33812
- "device-account": { title: "This account is run from a device", retry: "Use another pod", go: signIn }
33846
+ "device-account": { title: "This account is run from a device", retry: "Use another pod", go: signIn },
33847
+ "address-closed": { title: "This address is closed", retry: "Sign in with another account", go: signIn }
33813
33848
  }[e.code];
33814
33849
  if (e.code === "sign-in-refused" && once("fedipod-renewing")) {
33815
33850
  $("loading").hidden = true;