fedipod 1.40.0 → 1.43.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 +5 -0
- package/architecture.md +1 -3
- package/architecture.svg +93 -85
- package/gateway.md +7 -2
- package/lib/client/c2s.mjs +16 -5
- package/lib/client/masto/oauth.mjs +1 -1
- package/lib/connections/vault.mjs +1 -1
- package/lib/core/agent.mjs +720 -0
- package/lib/core/deliver.mjs +11 -1
- package/lib/core/gateway-move.mjs +58 -0
- package/lib/core/intake/group.mjs +10 -11
- package/lib/core/keys.mjs +1 -1
- package/lib/core/lease.mjs +4 -3
- package/lib/core/publisher/index.mjs +4 -6
- package/lib/core/publisher/notes.mjs +73 -4
- package/lib/core/remote.mjs +54 -0
- package/lib/core/shapes/shapes-text.mjs +11 -3
- package/lib/core/wire.mjs +4 -1
- package/lib/device/admin/index.mjs +4 -11
- package/lib/device/admin/routes/setup.mjs +60 -5
- package/lib/device/admin/server.mjs +18 -5
- package/lib/device/cli/commands/account.mjs +1 -1
- package/lib/device/cli/commands/run.mjs +2 -2
- package/lib/device/cli/commands/service.mjs +1 -1
- package/lib/device/cli/commands/setup.mjs +1 -1
- package/lib/device/cli/commands/state.mjs +1 -1
- package/lib/device/cli/context.mjs +1 -1
- package/lib/{gateway → device}/directory.mjs +1 -1
- package/lib/device/gateway-move.mjs +3 -58
- package/lib/device/home.mjs +3 -50
- package/lib/{client → device}/localapi.mjs +2 -2
- package/lib/device/remote.mjs +3 -54
- package/lib/gateway/copy.mjs +12 -3
- package/lib/gateway/front-core.mjs +16 -17
- package/lib/gateway/front-pages.mjs +31 -0
- package/lib/gateway/gateway-core.mjs +1 -1
- package/lib/gateway/held-mail.mjs +3 -0
- package/lib/gateway/keeper-session.mjs +10 -1
- package/lib/gateway/keeper.mjs +3 -0
- package/lib/gateway/masto-gateway.mjs +61 -12
- package/lib/gateway/outbox-door.mjs +31 -0
- package/lib/gateway/state-api.mjs +39 -7
- package/lib/server/embed.mjs +4 -4
- package/lib/shared/files.mjs +49 -0
- package/lib/shared/ua.mjs +3 -0
- package/lib/surface/index.mjs +7 -0
- package/lib/{device/admin → surface}/origins.mjs +1 -1
- package/lib/{device/admin → surface}/routes/gateway.mjs +7 -11
- package/lib/{device/admin → surface}/routes/lifecycle.mjs +3 -3
- package/lib/{device/admin → surface}/routes/owner.mjs +4 -60
- package/lib/{device/admin → surface}/routes/social.mjs +1 -1
- package/lib/{device/admin → surface}/static.mjs +6 -1
- package/lib/{device/admin → surface}/surface.mjs +19 -16
- package/package.json +23 -5
- package/run-agent.mjs +19 -720
- package/scripts/build-app.mjs +4 -0
- package/scripts/check-netlify-bundles.mjs +88 -0
- package/scripts/check-pod-calls.mjs +42 -0
- package/scripts/vendor-sanitize.mjs +27 -0
- package/vendor/idp-grant.cjs +11 -5
- package/vendor/sanitize-html.cjs +11456 -0
- package/web/app/agent.mjs +4 -2
- package/web/app/copy-mode.mjs +27 -3
- package/web/app/dist/boot.js.map +1 -1
- package/web/app/dist/sw.js +110 -24
- package/web/app/dist/sw.js.map +4 -4
- package/web/app-signin/app-signin.mjs +2 -2
- package/web/app-signin/index.html +13 -5
- package/browser.svg +0 -72
- /package/lib/{device/admin → surface}/routes/connections.mjs +0 -0
package/README.md
CHANGED
|
@@ -82,6 +82,11 @@ If your account turns a post down when it runs, you get a direct message from
|
|
|
82
82
|
yourself saying what was refused and why. Signed in, your outbox shows you
|
|
83
83
|
everything you've done, likes and follows included; everyone else sees only
|
|
84
84
|
your public posts.
|
|
85
|
+
While fedipod.net keeps your account running, a post from such an app goes out
|
|
86
|
+
in a moment, whether or not FediPod is open.
|
|
87
|
+
A comment you post from dokieli names nobody to send it to, so it goes to
|
|
88
|
+
nobody. It is kept on your pod at its own address, where the page you
|
|
89
|
+
commented on points to it. Your own timeline shows it as a private post.
|
|
85
90
|
|
|
86
91
|
**The manage page.** `manage`, in the bar's account group, opens it: your profile,
|
|
87
92
|
aliases, the gateway, key rotation, recovering posts, parking, moving to
|
package/architecture.md
CHANGED
|
@@ -28,9 +28,7 @@ work from, and which the gateway writes to the pod every fifteen minutes. See
|
|
|
28
28
|
Community Solid Server, so anyone with a pod on that server can opt in to a
|
|
29
29
|
Fediverse account fed by the server itself.
|
|
30
30
|
|
|
31
|
-

|
|
31
|
+

|
|
34
32
|
|
|
35
33
|
## Protocol conformance
|
|
36
34
|
|
package/architecture.svg
CHANGED
|
@@ -1,89 +1,97 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1300 680" font-family="Arial, Helvetica, sans-serif">
|
|
2
2
|
<defs>
|
|
3
|
-
<marker id="
|
|
4
|
-
<path d="M0,0 L10,5 L0,10 z" fill="#
|
|
3
|
+
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto-start-reverse">
|
|
4
|
+
<path d="M0,0 L10,5 L0,10 z" fill="#333"/>
|
|
5
5
|
</marker>
|
|
6
6
|
</defs>
|
|
7
|
-
<
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
<
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
<
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
<
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
7
|
+
<rect x="0" y="0" width="1300" height="680" fill="#ffffff"/>
|
|
8
|
+
|
|
9
|
+
<!-- boxes (draw.io palette, as browser.svg) -->
|
|
10
|
+
<g stroke-width="1.5">
|
|
11
|
+
<rect x="450" y="24" width="280" height="84" rx="6" fill="#dae8fc" stroke="#6c8ebf"/>
|
|
12
|
+
<rect x="260" y="175" width="650" height="215" rx="10" fill="#f5f5f5" stroke="#666666" stroke-dasharray="8 6"/>
|
|
13
|
+
<rect x="285" y="230" width="230" height="130" rx="6" fill="#d5e8d4" stroke="#82b366"/>
|
|
14
|
+
<rect x="665" y="230" width="230" height="130" rx="6" fill="#e1d5e7" stroke="#9673a6"/>
|
|
15
|
+
<rect x="40" y="500" width="300" height="110" rx="6" fill="#f8cecc" stroke="#b85450"/>
|
|
16
|
+
<rect x="780" y="500" width="300" height="110" rx="6" fill="#fff2cc" stroke="#d6b656"/>
|
|
17
|
+
<rect x="1040" y="250" width="220" height="95" rx="6" fill="#ffe6cc" stroke="#d79b00"/>
|
|
18
|
+
</g>
|
|
19
|
+
|
|
20
|
+
<!-- box labels -->
|
|
21
|
+
<g fill="#111" text-anchor="middle">
|
|
22
|
+
<text x="590" y="60" font-size="20" font-weight="bold">Open Social Web</text>
|
|
23
|
+
<text x="590" y="88" font-size="16">Mastodon · Pixelfed · Bluesky …</text>
|
|
24
|
+
|
|
25
|
+
<text x="280" y="206" font-size="22" font-weight="bold" text-anchor="start">FediPod</text>
|
|
26
|
+
|
|
27
|
+
<text x="400" y="266" font-size="20" font-weight="bold">BrowserAgent</text>
|
|
28
|
+
<text x="400" y="294" font-size="16">FediPod in any browser,</text>
|
|
29
|
+
<text x="400" y="318" font-size="16">with Sengi or Phanpy</text>
|
|
30
|
+
|
|
31
|
+
<text x="780" y="266" font-size="20" font-weight="bold">Gateway</text>
|
|
32
|
+
<text x="780" y="294" font-size="16">at fedipod.net, keeps</text>
|
|
33
|
+
<text x="780" y="318" font-size="16">your account running</text>
|
|
34
|
+
<text x="780" y="342" font-size="16">from a working copy</text>
|
|
35
|
+
|
|
36
|
+
<text x="190" y="536" font-size="20" font-weight="bold">Your Pod</text>
|
|
37
|
+
<text x="190" y="564" font-size="16">your key, your posts</text>
|
|
38
|
+
<text x="190" y="588" font-size="16">and your account's data</text>
|
|
39
|
+
|
|
40
|
+
<text x="930" y="536" font-size="20" font-weight="bold">Mastodon Apps</text>
|
|
41
|
+
<text x="930" y="564" font-size="16">Elk · Ivory · Tusky · Fedilab</text>
|
|
42
|
+
<text x="930" y="588" font-size="16">on your phone or desktop</text>
|
|
43
|
+
|
|
44
|
+
<text x="1150" y="289" font-size="20" font-weight="bold">C2S Apps</text>
|
|
45
|
+
<text x="1150" y="317" font-size="16">box, Dokieli</text>
|
|
46
|
+
</g>
|
|
47
|
+
|
|
48
|
+
<!-- arrows -->
|
|
49
|
+
<g stroke="#333" stroke-width="1.6" fill="none">
|
|
50
|
+
<!-- Open Social Web <-> BrowserAgent (ATProto, Bluesky) -->
|
|
51
|
+
<line x1="520" y1="108" x2="410" y2="230" marker-start="url(#arrow)" marker-end="url(#arrow)"/>
|
|
52
|
+
<!-- Open Social Web <-> Gateway (ActivityPub mail) -->
|
|
53
|
+
<line x1="660" y1="108" x2="770" y2="230" marker-start="url(#arrow)" marker-end="url(#arrow)"/>
|
|
54
|
+
<!-- BrowserAgent <-> Gateway -->
|
|
55
|
+
<line x1="515" y1="298" x2="665" y2="298" marker-start="url(#arrow)" marker-end="url(#arrow)"/>
|
|
56
|
+
<!-- Your Pod -> Open Social Web (public posts and profile) -->
|
|
57
|
+
<path d="M100,500 C0,400 0,110 450,70" marker-end="url(#arrow)"/>
|
|
58
|
+
<!-- BrowserAgent <-> Your Pod (Solid) -->
|
|
59
|
+
<line x1="350" y1="360" x2="230" y2="500" marker-start="url(#arrow)" marker-end="url(#arrow)"/>
|
|
60
|
+
<!-- Gateway <-> Your Pod (stores the account's data) -->
|
|
61
|
+
<line x1="720" y1="360" x2="340" y2="560" marker-start="url(#arrow)" marker-end="url(#arrow)"/>
|
|
62
|
+
<!-- Mastodon Apps <-> Gateway -->
|
|
63
|
+
<line x1="900" y1="500" x2="840" y2="360" marker-start="url(#arrow)" marker-end="url(#arrow)"/>
|
|
64
|
+
<!-- C2S Apps <-> Gateway -->
|
|
65
|
+
<line x1="1040" y1="298" x2="895" y2="298" marker-start="url(#arrow)" marker-end="url(#arrow)"/>
|
|
66
|
+
</g>
|
|
67
|
+
|
|
68
|
+
<!-- arrow labels -->
|
|
69
|
+
<g fill="#333" font-size="16">
|
|
70
|
+
<text x="470" y="140" text-anchor="end" font-weight="bold" fill="#111">ATProto Protocol</text>
|
|
71
|
+
<text x="470" y="160" text-anchor="end">for Bluesky</text>
|
|
72
|
+
|
|
73
|
+
<text x="712" y="140" text-anchor="start" font-weight="bold" fill="#111">S2S Protocol</text>
|
|
74
|
+
<text x="712" y="160" text-anchor="start">mail in, posts out</text>
|
|
75
|
+
|
|
76
|
+
<text x="590" y="286" text-anchor="middle">serves the app</text>
|
|
77
|
+
<text x="590" y="320" text-anchor="middle">posts out</text>
|
|
78
|
+
|
|
79
|
+
<text x="968" y="286" text-anchor="middle" font-weight="bold" fill="#111">C2S Protocol</text>
|
|
80
|
+
|
|
81
|
+
<text x="85" y="250" text-anchor="start">other servers read</text>
|
|
82
|
+
<text x="85" y="270" text-anchor="start">your public posts</text>
|
|
83
|
+
<text x="85" y="290" text-anchor="start">and profile</text>
|
|
84
|
+
|
|
85
|
+
<text x="272" y="420" text-anchor="end" font-weight="bold" fill="#111">Solid Protocol</text>
|
|
86
|
+
<text x="272" y="440" text-anchor="end">reads & writes your pod</text>
|
|
87
|
+
|
|
88
|
+
<text x="520" y="490" text-anchor="start">stores the working copy</text>
|
|
89
|
+
<text x="520" y="510" text-anchor="start">on your pod</text>
|
|
90
|
+
|
|
91
|
+
<text x="894" y="415" text-anchor="start" font-weight="bold" fill="#111">Mastodon client API</text>
|
|
92
|
+
<text x="894" y="435" text-anchor="start">sign in at fedipod.net;</text>
|
|
93
|
+
<text x="894" y="455" text-anchor="start">notifications to your phone</text>
|
|
94
|
+
</g>
|
|
95
|
+
|
|
96
|
+
<text x="1260" y="660" text-anchor="end" font-size="16" fill="#555">© 2026 Jeff Zucker · CC BY-SA 4.0</text>
|
|
89
97
|
</svg>
|
package/gateway.md
CHANGED
|
@@ -117,6 +117,11 @@ there, signed in at your pod. The door checks that the token is yours, puts the
|
|
|
117
117
|
post in your inbox marked as yours, and answers with the address the post will
|
|
118
118
|
have. Your agent publishes it and sends it to your followers the next time it
|
|
119
119
|
runs: for a browser account, the next time you open the site.
|
|
120
|
+
While the gateway keeps a browser account running, it starts the account's
|
|
121
|
+
keeper as the post arrives, so the post goes out in a moment.
|
|
122
|
+
A comment from dokieli names nobody to send it to, so it is sent to nobody. It
|
|
123
|
+
is kept at its own address for the page it is on to point at, and only your
|
|
124
|
+
own outbox lists it.
|
|
120
125
|
|
|
121
126
|
A post with no audience of its own goes out as a public post. A post that is
|
|
122
127
|
not a note, an annotation say, is kept as the app sent it, under your name.
|
|
@@ -193,8 +198,8 @@ you connected elsewhere are never in the copy; they stay on your pod only.
|
|
|
193
198
|
While the gateway keeps your account running, any Mastodon app can use it:
|
|
194
199
|
elk.zone, Ivory, Tusky, Phanpy and the rest. In the app, give `fedipod.net` as
|
|
195
200
|
your server. The app sends you to a fedipod.net page that asks for your
|
|
196
|
-
address
|
|
197
|
-
fedipod.net. The app then reads your timeline and notifications from your
|
|
201
|
+
Fediverse address or your WebID and signs you in at your own pod; no password
|
|
202
|
+
is typed on fedipod.net. The app then reads your timeline and notifications from your
|
|
198
203
|
account's copy at the gateway, and posts, boosts, likes and follows as you.
|
|
199
204
|
|
|
200
205
|
An app checks for new posts every minute or so while it is open; there are no
|
package/lib/client/c2s.mjs
CHANGED
|
@@ -48,6 +48,10 @@ const ACTIVITY_TYPES = new Set([
|
|
|
48
48
|
|
|
49
49
|
const arr = (v) => (v === undefined || v === null ? [] : Array.isArray(v) ? v : [v]);
|
|
50
50
|
const idOf = (v) => (typeof v === 'string' ? v : v?.id || null);
|
|
51
|
+
// An audience address. The public collection has three valid spellings
|
|
52
|
+
// (ActivityPub §5.6); reading a post as JSON-LD can yield the short one.
|
|
53
|
+
const PUBLIC_SPELLINGS = new Set(['Public', 'as:Public']);
|
|
54
|
+
const audienceOf = (v) => { const id = idOf(v); return PUBLIC_SPELLINGS.has(id) ? wire.PUBLIC : id; };
|
|
51
55
|
|
|
52
56
|
function readBody(req) {
|
|
53
57
|
return new Promise((resolve, reject) => {
|
|
@@ -63,6 +67,9 @@ function readBody(req) {
|
|
|
63
67
|
});
|
|
64
68
|
}
|
|
65
69
|
|
|
70
|
+
const isAnnotation = (o) => arr(o?.type).some((t) => t === 'Annotation' || t === 'oa:Annotation'
|
|
71
|
+
|| t === 'http://www.w3.org/ns/oa#Annotation');
|
|
72
|
+
|
|
66
73
|
export class C2S {
|
|
67
74
|
constructor({ agent, log = console.log, auth, scheme = null, mount = '' }) {
|
|
68
75
|
this.agent = agent;
|
|
@@ -345,8 +352,8 @@ export class C2S {
|
|
|
345
352
|
// every client means by a post with no audience chosen, and what a client
|
|
346
353
|
// that never addresses (dokieli's annotations) needs.
|
|
347
354
|
visibilityOf(activity, object) {
|
|
348
|
-
const to = arr(activity.to ?? object?.to).map(
|
|
349
|
-
const cc = arr(activity.cc ?? object?.cc).map(
|
|
355
|
+
const to = arr(activity.to ?? object?.to).map(audienceOf);
|
|
356
|
+
const cc = arr(activity.cc ?? object?.cc).map(audienceOf);
|
|
350
357
|
// Blind copies alone are still an audience: a post for them and nobody
|
|
351
358
|
// else is direct, never public.
|
|
352
359
|
const blind = arr(activity.bto ?? object?.bto).length + arr(activity.bcc ?? object?.bcc).length;
|
|
@@ -386,7 +393,7 @@ export class C2S {
|
|
|
386
393
|
// in to/cc are listed and delivered to; the ones in bto/bcc are delivered
|
|
387
394
|
// to and never listed (§6).
|
|
388
395
|
addressedActors(activity, object) {
|
|
389
|
-
const pick = (...fields) => [...new Set(fields.flatMap((f) => arr(activity[f] ?? object?.[f]).map(
|
|
396
|
+
const pick = (...fields) => [...new Set(fields.flatMap((f) => arr(activity[f] ?? object?.[f]).map(audienceOf)))]
|
|
390
397
|
.filter((a) => typeof a === 'string' && /^https?:\/\//u.test(a)
|
|
391
398
|
&& a !== wire.PUBLIC && a !== this.urls.followers && a !== this.urls.actor);
|
|
392
399
|
// `audience` is who the post is for, a group most often: delivered to and
|
|
@@ -417,12 +424,16 @@ export class C2S {
|
|
|
417
424
|
const visibility = this.visibilityOf(activity, object);
|
|
418
425
|
const { also, deliverTo } = this.addressedActors(activity, object);
|
|
419
426
|
// Not a Note and not a poll: stored as sent, under this actor, and
|
|
420
|
-
// the Create around it delivered — a Web Annotation, for one.
|
|
427
|
+
// the Create around it delivered — a Web Annotation, for one. An
|
|
428
|
+
// annotation addressed to nobody, as dokieli sends one, is delivered
|
|
429
|
+
// to nobody: kept at its address for the page it is on to point at.
|
|
421
430
|
if (makes !== 'Note' && makes !== 'Question') {
|
|
422
431
|
const asSent = raw && typeof raw === 'object' && !Array.isArray(raw)
|
|
423
432
|
? (ACTIVITY_TYPES.has(raw.type) ? (raw.object && typeof raw.object === 'object' ? raw.object : null) : raw)
|
|
424
433
|
: null;
|
|
425
|
-
const
|
|
434
|
+
const unaddressed = isAnnotation(asSent || object)
|
|
435
|
+
&& ['to', 'cc', 'bto', 'bcc', 'audience'].every((f) => !arr(activity[f] ?? object[f]).length);
|
|
436
|
+
const made = await agent.publisher.publishObject(asSent || object, { visibility, slug, also, deliverTo, unaddressed });
|
|
426
437
|
return reply(201, { id: made.createId, object: made.id }, { location: made.createId });
|
|
427
438
|
}
|
|
428
439
|
// `source.content` is the client's plain text when it sends one; bare
|
|
@@ -53,7 +53,7 @@ function sendPodSigninPage(res, client, webId, mount = '') {
|
|
|
53
53
|
try { where = client.redirect ? new URL(client.redirect).host : ''; } catch { /* oob or blank */ }
|
|
54
54
|
const who = client.name ? escapeHtml(client.name) : (where ? escapeHtml(where) : 'A client');
|
|
55
55
|
asking = `<p><strong>${who}</strong> is asking to use your account`
|
|
56
|
-
+ `${where ? `, and will be sent back to <code>${escapeHtml(where)}</code>` : ''}.</p>`;
|
|
56
|
+
+ `${where ? `, and you will be sent back to <code>${escapeHtml(where)}</code>` : ''}.</p>`;
|
|
57
57
|
}
|
|
58
58
|
res.writeHead(200, { 'content-type': 'text/html; charset=utf-8', 'cache-control': 'no-cache' });
|
|
59
59
|
res.end(`<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
// server use it before they answer.
|
|
19
19
|
import fs from 'node:fs';
|
|
20
20
|
import path from 'node:path';
|
|
21
|
-
import { writeJsonAtomic } from '../
|
|
21
|
+
import { writeJsonAtomic } from '../shared/files.mjs';
|
|
22
22
|
|
|
23
23
|
/** What every connection document in pod state is named from. */
|
|
24
24
|
export const CONNECTION_PREFIX = 'conn-';
|