fedipod 1.29.2 → 1.32.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 +1 -1
- package/device-agent.md +9 -2
- package/gateway.md +43 -4
- package/gui.md +2 -2
- package/lib/core/publisher/index.mjs +3 -0
- package/lib/core/wire.mjs +10 -1
- package/lib/device/cli/commands/setup.mjs +6 -2
- package/lib/device/gateway-move.mjs +58 -0
- package/lib/device/migrate.mjs +3 -1
- package/lib/device/setup.mjs +67 -7
- package/lib/gateway/front-core.mjs +34 -14
- package/lib/gateway/gateway-core.mjs +15 -6
- package/lib/gateway/notices.mjs +84 -0
- package/lib/gateway/quiet.mjs +184 -0
- package/package.json +1 -1
- package/run-agent.mjs +5 -0
- package/scripts/stage-site.mjs +6 -3
- package/web/admin/bar.css +44 -10
- package/web/admin/client/index.html +29 -2
- package/web/admin/gateway.js +19 -0
- package/web/admin/index.html +46 -3
- package/web/admin/notices-bar.js +90 -0
- package/web/admin/record.js +1 -0
- package/web/admin/setup/index.html +29 -2
- package/web/admin/upkeep.js +5 -1
- package/web/app/admin-facade.mjs +24 -0
- package/web/app/agent.mjs +48 -0
- package/web/app/boot.mjs +2 -0
- package/web/app/dist/boot.js +3 -1
- package/web/app/dist/boot.js.map +2 -2
- package/web/app/dist/sw.js +96 -3
- package/web/app/dist/sw.js.map +3 -3
- package/web/front/admin.html +3 -1
- package/web/front/notices.html +69 -0
- package/web/front/notices.js +107 -0
package/web/app/dist/sw.js
CHANGED
|
@@ -9700,6 +9700,7 @@ __export(wire_exports, {
|
|
|
9700
9700
|
DEFAULT_ROOT: () => DEFAULT_ROOT,
|
|
9701
9701
|
FOLLOWERS_PAGE_SIZE: () => FOLLOWERS_PAGE_SIZE,
|
|
9702
9702
|
HASHTAG_RE: () => HASHTAG_RE,
|
|
9703
|
+
LEMMY_CTX: () => LEMMY_CTX,
|
|
9703
9704
|
MENTION_RE: () => MENTION_RE,
|
|
9704
9705
|
OUTBOX_PAGE_SIZE: () => OUTBOX_PAGE_SIZE,
|
|
9705
9706
|
POLICY_CTX: () => POLICY_CTX,
|
|
@@ -9812,7 +9813,8 @@ function actorDoc({
|
|
|
9812
9813
|
inbox = null,
|
|
9813
9814
|
outbox = null,
|
|
9814
9815
|
oauthAuthorize = null,
|
|
9815
|
-
oauthToken = null
|
|
9816
|
+
oauthToken = null,
|
|
9817
|
+
postingRestrictedToMods = null
|
|
9816
9818
|
}) {
|
|
9817
9819
|
const context = [AS_CTX, SEC_CTX];
|
|
9818
9820
|
context.push({ toot: "http://joinmastodon.org/ns#", featured: { "@id": "toot:featured", "@type": "@id" } });
|
|
@@ -9835,6 +9837,7 @@ function actorDoc({
|
|
|
9835
9837
|
});
|
|
9836
9838
|
}
|
|
9837
9839
|
if (webId || aliases.length) context.push({ alsoKnownAs: { "@id": "as:alsoKnownAs", "@type": "@id" } });
|
|
9840
|
+
if (kind === "group" && postingRestrictedToMods !== null) context.push(LEMMY_CTX);
|
|
9838
9841
|
if (fields.length) {
|
|
9839
9842
|
context.push({ schema: "http://schema.org#", PropertyValue: "schema:PropertyValue", value: "schema:value" });
|
|
9840
9843
|
}
|
|
@@ -9853,6 +9856,7 @@ function actorDoc({
|
|
|
9853
9856
|
}] } : {},
|
|
9854
9857
|
...movedTo ? { movedTo } : {},
|
|
9855
9858
|
...webId || aliases.length ? { alsoKnownAs: [...webId ? [webId] : [], ...aliases] } : {},
|
|
9859
|
+
...kind === "group" && postingRestrictedToMods !== null ? { postingRestrictedToMods: !!postingRestrictedToMods } : {},
|
|
9856
9860
|
preferredUsername: handle7,
|
|
9857
9861
|
name: name || handle7,
|
|
9858
9862
|
// The bio, and the avatar. For a group, `summary` is where what the group
|
|
@@ -10375,7 +10379,7 @@ function addRemoveActivity({ urls, type, object, target, serial }) {
|
|
|
10375
10379
|
target
|
|
10376
10380
|
};
|
|
10377
10381
|
}
|
|
10378
|
-
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_ESCAPES2, MENTION_RE, HASHTAG_RE, QUOTE_CTX, POLICY_CTX;
|
|
10382
|
+
var import_sanitize_html, AS_CTX, LEMMY_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_ESCAPES2, MENTION_RE, HASHTAG_RE, QUOTE_CTX, POLICY_CTX;
|
|
10379
10383
|
var init_wire = __esm({
|
|
10380
10384
|
"lib/core/wire.mjs"() {
|
|
10381
10385
|
init_urls();
|
|
@@ -10383,6 +10387,7 @@ var init_wire = __esm({
|
|
|
10383
10387
|
init_profile_page();
|
|
10384
10388
|
import_sanitize_html = __toESM(require_sanitize_html(), 1);
|
|
10385
10389
|
AS_CTX = "https://www.w3.org/ns/activitystreams";
|
|
10390
|
+
LEMMY_CTX = "https://join-lemmy.org/context.json";
|
|
10386
10391
|
SEC_CTX = "https://w3id.org/security/v1";
|
|
10387
10392
|
PUBLIC = "https://www.w3.org/ns/activitystreams#Public";
|
|
10388
10393
|
DEFAULT_ROOT = "fedipod/";
|
|
@@ -57180,6 +57185,9 @@ var Publisher = class {
|
|
|
57180
57185
|
fields: this.config.fields || [],
|
|
57181
57186
|
webId: this.remote.webId || null,
|
|
57182
57187
|
aliases: this.config.aliases || [],
|
|
57188
|
+
// A group says whether only its moderators may open posts (Lemmy's
|
|
57189
|
+
// term); a person's actor carries nothing of the kind.
|
|
57190
|
+
postingRestrictedToMods: this.config.kind === "group" ? !!this.config.postingRestrictedToMods : null,
|
|
57183
57191
|
moderators,
|
|
57184
57192
|
pendingFollowers: priv ? urls.pendingFollowers : null,
|
|
57185
57193
|
pendingFollowing: priv ? urls.pendingFollowing : null,
|
|
@@ -69911,6 +69919,8 @@ var ADMIN_PATHS = /* @__PURE__ */ new Set([
|
|
|
69911
69919
|
"/park",
|
|
69912
69920
|
"/revive",
|
|
69913
69921
|
"/takeover",
|
|
69922
|
+
"/gateway/pause",
|
|
69923
|
+
"/gateway/close",
|
|
69914
69924
|
"/fediacct/connect",
|
|
69915
69925
|
"/fediacct/disconnect",
|
|
69916
69926
|
"/fediacct/callback"
|
|
@@ -70015,7 +70025,11 @@ var AdminFacade = class {
|
|
|
70015
70025
|
frontActor: g?.frontActor || null,
|
|
70016
70026
|
mode: g?.mode || "off",
|
|
70017
70027
|
hasSecret: !!g?.hmacSecret,
|
|
70018
|
-
stats: a.store.read("gateway-stats.json", { verified: 0, unverified: 0, lastAt: null })
|
|
70028
|
+
stats: a.store.read("gateway-stats.json", { verified: 0, unverified: 0, lastAt: null }),
|
|
70029
|
+
// The account's standing at the gateway — paused, closed — as the
|
|
70030
|
+
// gateway last said it (agent.mjs tellGateway). The record page
|
|
70031
|
+
// shows its pause and close controls only when this is here.
|
|
70032
|
+
standing: a.gatewayStanding || null
|
|
70019
70033
|
});
|
|
70020
70034
|
}
|
|
70021
70035
|
case "/deadletter":
|
|
@@ -70159,6 +70173,24 @@ var AdminFacade = class {
|
|
|
70159
70173
|
if (!await a.requestTakeover?.()) return json2(503, { error: "the lease could not be taken \u2014 the pod refused the write" });
|
|
70160
70174
|
return json2(200, { ok: true, mode: a.status().mode });
|
|
70161
70175
|
}
|
|
70176
|
+
// The account's standing at its gateway: paused by its owner, or closed
|
|
70177
|
+
// for good (front-core: accounts that go quiet). Both go to the gateway
|
|
70178
|
+
// with the pod session as proof, and its answer is the new standing.
|
|
70179
|
+
case "/gateway/pause": {
|
|
70180
|
+
if (typeof body.paused !== "boolean") return json2(400, { error: "paused must be true or false" });
|
|
70181
|
+
if (!a.pauseAtGateway || !a.gatewayApi) return json2(501, { error: "this account is not at a gateway" });
|
|
70182
|
+
const r = await a.pauseAtGateway(body.paused);
|
|
70183
|
+
if (!r) return json2(502, { error: "the gateway could not be reached" });
|
|
70184
|
+
return json2(r.status === 200 ? 200 : r.status, r);
|
|
70185
|
+
}
|
|
70186
|
+
case "/gateway/close": {
|
|
70187
|
+
const handle7 = String(a.store.getConfig()?.handle || "").toLowerCase();
|
|
70188
|
+
if (!handle7 || String(body.confirm || "").toLowerCase() !== handle7) return json2(400, { error: "type the handle to confirm" });
|
|
70189
|
+
if (!a.closeAtGateway || !a.gatewayApi) return json2(501, { error: "this account is not at a gateway" });
|
|
70190
|
+
const r = await a.closeAtGateway();
|
|
70191
|
+
if (!r) return json2(502, { error: "the gateway could not be reached" });
|
|
70192
|
+
return json2(r.status === 200 ? 200 : r.status, r);
|
|
70193
|
+
}
|
|
70162
70194
|
// Going quiet, and coming back. The record page's active/parked select.
|
|
70163
70195
|
case "/park": {
|
|
70164
70196
|
if (!await a.requestTakeover?.()) return json2(503, { error: "another device is active for this pod \u2014 park from there" });
|
|
@@ -71700,10 +71732,46 @@ var BrowserAgent = class _BrowserAgent {
|
|
|
71700
71732
|
this.log("took over from the other device (action here)");
|
|
71701
71733
|
return true;
|
|
71702
71734
|
}
|
|
71735
|
+
// One call to the gateway's owner API, proved with the pod session. What
|
|
71736
|
+
// comes back on 200 or 410 is the account's standing there, kept for the
|
|
71737
|
+
// manage page; anything else is logged and forgotten.
|
|
71738
|
+
async tellGateway(what, body) {
|
|
71739
|
+
if (!this.gatewayApi) return null;
|
|
71740
|
+
let res;
|
|
71741
|
+
try {
|
|
71742
|
+
res = await this.sessionFetch(`${this.gatewayApi}/${what}`, {
|
|
71743
|
+
method: "POST",
|
|
71744
|
+
headers: { "content-type": "application/json" },
|
|
71745
|
+
body: JSON.stringify(body)
|
|
71746
|
+
});
|
|
71747
|
+
} catch (e) {
|
|
71748
|
+
this.log(`gateway ${what}: ${e.message}`);
|
|
71749
|
+
return null;
|
|
71750
|
+
}
|
|
71751
|
+
const json2 = await res.json().catch(() => ({}));
|
|
71752
|
+
const out = { status: res.status, ...json2 };
|
|
71753
|
+
if (res.status === 200 || res.status === 410) this.gatewayStanding = out;
|
|
71754
|
+
else this.log(`gateway ${what}: ${res.status} ${json2.error || ""}`);
|
|
71755
|
+
return out;
|
|
71756
|
+
}
|
|
71757
|
+
openAtGateway() {
|
|
71758
|
+
return this.tellGateway("open", { handle: this.doorKey });
|
|
71759
|
+
}
|
|
71760
|
+
pauseAtGateway(paused) {
|
|
71761
|
+
return this.tellGateway("pause", { handle: this.doorKey, paused: !!paused });
|
|
71762
|
+
}
|
|
71763
|
+
closeAtGateway() {
|
|
71764
|
+
return this.tellGateway("close", { handle: this.doorKey, confirm: true });
|
|
71765
|
+
}
|
|
71766
|
+
static OPEN_EVERY_MS = 60 * 6e4;
|
|
71703
71767
|
// Become the active agent: renew the lease, then start what a viewer skips —
|
|
71704
71768
|
// publish the face, drain the inbox, run the mirrors.
|
|
71705
71769
|
async goActive() {
|
|
71706
71770
|
this.viewer = false;
|
|
71771
|
+
clearInterval(this._openTimer);
|
|
71772
|
+
this._openTimer = setInterval(() => {
|
|
71773
|
+
this.openAtGateway();
|
|
71774
|
+
}, _BrowserAgent.OPEN_EVERY_MS);
|
|
71707
71775
|
this.lease.onLost = () => this.demote();
|
|
71708
71776
|
this.lease.startRenewal();
|
|
71709
71777
|
try {
|
|
@@ -71730,6 +71798,8 @@ var BrowserAgent = class _BrowserAgent {
|
|
|
71730
71798
|
this.viewer = true;
|
|
71731
71799
|
this.log("another device took over \u2014 read-only here");
|
|
71732
71800
|
this.lease.stopRenewal();
|
|
71801
|
+
clearInterval(this._openTimer);
|
|
71802
|
+
this._openTimer = null;
|
|
71733
71803
|
this.intake?.stop?.();
|
|
71734
71804
|
this.deliverer?.stop?.();
|
|
71735
71805
|
this.importer?.stop?.();
|
|
@@ -71843,6 +71913,29 @@ var BrowserAgent = class _BrowserAgent {
|
|
|
71843
71913
|
sessionFetch: session.fetch,
|
|
71844
71914
|
onGone: () => this.publisher.publishCollections({ followers: true })
|
|
71845
71915
|
});
|
|
71916
|
+
this.gatewayApi = null;
|
|
71917
|
+
try {
|
|
71918
|
+
if (config.gateway?.url) this.gatewayApi = `${new URL(config.gateway.url).origin}/api`;
|
|
71919
|
+
} catch {
|
|
71920
|
+
this.gatewayApi = null;
|
|
71921
|
+
}
|
|
71922
|
+
this.doorKey = doorKeyOf(config.gateway?.url) || config.handle;
|
|
71923
|
+
this.gatewayStanding = null;
|
|
71924
|
+
const standing = await this.openAtGateway();
|
|
71925
|
+
if (standing?.status === 410) {
|
|
71926
|
+
const host = (() => {
|
|
71927
|
+
try {
|
|
71928
|
+
return new URL(this.gatewayApi).host;
|
|
71929
|
+
} catch {
|
|
71930
|
+
return "the gateway";
|
|
71931
|
+
}
|
|
71932
|
+
})();
|
|
71933
|
+
const address = this.doorKey.includes("@") ? `@${this.doorKey}` : `@${this.doorKey}@${host}`;
|
|
71934
|
+
const why = standing.closedBy === "owner" ? "you closed it" : "nothing opened it for six months";
|
|
71935
|
+
const e = new Error(`${address} is closed at ${host}: ${why}, and a closed address does not come back. Everything on your pod is untouched. The sign-in used here was ${webId}.`);
|
|
71936
|
+
e.code = "address-closed";
|
|
71937
|
+
throw e;
|
|
71938
|
+
}
|
|
71846
71939
|
this.publisher = new Publisher({
|
|
71847
71940
|
config: this.store.getConfig(),
|
|
71848
71941
|
remote: this.remote,
|