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/lib/server/embed.mjs
CHANGED
|
@@ -16,12 +16,12 @@ import path from 'node:path';
|
|
|
16
16
|
|
|
17
17
|
import { createRequire } from 'node:module';
|
|
18
18
|
|
|
19
|
-
import { Agent } from '
|
|
20
|
-
import { RemotePod } from '../
|
|
19
|
+
import { Agent } from '../core/agent.mjs';
|
|
20
|
+
import { RemotePod } from '../core/remote.mjs';
|
|
21
21
|
import { apUrls, DEFAULT_ROOT } from '../core/wire.mjs';
|
|
22
22
|
import { handleDelivery } from '../gateway/gateway-core.mjs';
|
|
23
|
-
import { writeJsonAtomic } from '../
|
|
24
|
-
import { buildAdminSurface } from '../
|
|
23
|
+
import { writeJsonAtomic } from '../shared/files.mjs';
|
|
24
|
+
import { buildAdminSurface } from '../surface/index.mjs';
|
|
25
25
|
import { FixedAuthorities } from '../shared/guard.mjs';
|
|
26
26
|
import { CONNECTION_PREFIX, fileVault, moveVault, podVault } from '../connections/vault.mjs';
|
|
27
27
|
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// files.mjs — writing a file without a window in which it is half there, and
|
|
2
|
+
// showing a path the way a person types it.
|
|
3
|
+
//
|
|
4
|
+
// The files written this way are the ones nothing can rebuild: keys.json is
|
|
5
|
+
// the private key remote servers have cached, and credential.json holds a
|
|
6
|
+
// secret a Solid server mints once and will not mint again. A plain
|
|
7
|
+
// writeFileSync truncates first, so a crash, a power loss or a full disk
|
|
8
|
+
// between truncate and write leaves an empty or half-written file where the
|
|
9
|
+
// identity used to be.
|
|
10
|
+
//
|
|
11
|
+
// rename(2) is atomic on POSIX, so a reader — or a backup running at the wrong
|
|
12
|
+
// moment — sees either the old file or the new one. fsync before it, so the
|
|
13
|
+
// content is on the platter and not merely in the page cache when the rename
|
|
14
|
+
// lands. Windows has no atomic rename over an existing file; there the copy
|
|
15
|
+
// path is still better than a truncate, and this is a best effort by design.
|
|
16
|
+
|
|
17
|
+
import fs from 'node:fs';
|
|
18
|
+
import os from 'node:os';
|
|
19
|
+
import path from 'node:path';
|
|
20
|
+
import { fileURLToPath } from 'node:url';
|
|
21
|
+
|
|
22
|
+
export function writeFileAtomic(file, body, { mode = 0o600 } = {}) {
|
|
23
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
24
|
+
fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
|
|
25
|
+
const fd = fs.openSync(tmp, 'w', mode);
|
|
26
|
+
try {
|
|
27
|
+
fs.writeFileSync(fd, body);
|
|
28
|
+
fs.fsyncSync(fd);
|
|
29
|
+
} finally {
|
|
30
|
+
fs.closeSync(fd);
|
|
31
|
+
}
|
|
32
|
+
fs.renameSync(tmp, file);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// The same, for the JSON these files all happen to be.
|
|
36
|
+
export function writeJsonAtomic(file, obj, opts) {
|
|
37
|
+
writeFileAtomic(file, JSON.stringify(obj, null, 2) + '\n', opts);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// A path for display: the home directory becomes `~`, a `file:` URL is shown
|
|
41
|
+
// as the path it is. Never used to build a path — `~` is the shell's, not the
|
|
42
|
+
// filesystem's, and anything that resolves one must expand it first.
|
|
43
|
+
export function tildify(p, homedir = os.homedir()) {
|
|
44
|
+
if (!p) return p;
|
|
45
|
+
let s = String(p);
|
|
46
|
+
if (s.startsWith('file://')) { try { s = fileURLToPath(s); } catch { return s; } }
|
|
47
|
+
if (s === homedir) return '~';
|
|
48
|
+
return s.startsWith(homedir + path.sep) ? '~' + s.slice(homedir.length) : s;
|
|
49
|
+
}
|
package/lib/shared/ua.mjs
CHANGED
|
@@ -27,3 +27,6 @@ try {
|
|
|
27
27
|
// Renamed 2026-07-31, once solidcommunity.net was back up and both actors there
|
|
28
28
|
// were retired: the name in their logs no longer has to match the name in here.
|
|
29
29
|
export const USER_AGENT = `fedipod/${version} (+https://github.com/jeff-zucker/FediPod)`;
|
|
30
|
+
// The same version, for anything else that reports it (the publisher's
|
|
31
|
+
// nodeinfo and status), read the same best-effort way.
|
|
32
|
+
export const AGENT_VERSION = version;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// The identity's HTTP surface: the Mastodon client API and OAuth, the
|
|
2
|
+
// ActivityPub client-to-server door, nodeinfo, the owner's door and routes,
|
|
3
|
+
// and the pages and client served off disk. buildAdminSurface returns one
|
|
4
|
+
// request handler; whatever listens hands it requests.
|
|
5
|
+
|
|
6
|
+
export { buildAdminSurface } from './surface.mjs';
|
|
7
|
+
export { namedOrigin, secureOrigin, wsOrigins } from './origins.mjs';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// origins.mjs — the addresses an identity answers on, as a browser must be
|
|
2
2
|
// sent to them.
|
|
3
3
|
|
|
4
|
-
import { allowedAuthorities, hostLabel } from '
|
|
4
|
+
import { allowedAuthorities, hostLabel } from '../shared/guard.mjs';
|
|
5
5
|
|
|
6
6
|
// The origin a browser should be sent to for an identity. A browser keys
|
|
7
7
|
// storage per ORIGIN, so linking every identity at localhost:<port> files them
|
|
@@ -4,10 +4,9 @@
|
|
|
4
4
|
import crypto from 'node:crypto';
|
|
5
5
|
import fs from 'node:fs';
|
|
6
6
|
import path from 'node:path';
|
|
7
|
-
import { spawn } from 'node:child_process';
|
|
8
7
|
|
|
9
8
|
export async function post(p, body, ctx, req, res) { // eslint-disable-line no-unused-vars
|
|
10
|
-
const { agent, log, allowed, embedded, port, publicOrigin, versionOnDisk, isGroup } = ctx; // eslint-disable-line no-unused-vars
|
|
9
|
+
const { agent, log, allowed, embedded, port, publicOrigin, versionOnDisk, isGroup, restart } = ctx; // eslint-disable-line no-unused-vars
|
|
11
10
|
const json = (res, status, obj) => { ctx.json(res, status, obj); return true; };
|
|
12
11
|
switch (p) {
|
|
13
12
|
// The inbox gateway lifecycle (opt-in). `configure` points at a
|
|
@@ -50,15 +49,10 @@ export async function post(p, body, ctx, req, res) { // eslint-disable-line no
|
|
|
50
49
|
const root = cfg.root ? (cfg.root.endsWith('/') ? cfg.root : `${cfg.root}/`) : 'fedipod/';
|
|
51
50
|
return `${base}${root}ap/actor`;
|
|
52
51
|
};
|
|
53
|
-
//
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const child = spawn(process.execPath, process.argv.slice(1),
|
|
58
|
-
{ detached: true, stdio: 'ignore', env: process.env });
|
|
59
|
-
child.unref();
|
|
60
|
-
setTimeout(() => process.exit(0), 300);
|
|
61
|
-
}, 200);
|
|
52
|
+
// Fronting and un-fronting rename every id, which only a restart
|
|
53
|
+
// applies. A host that cannot restart itself refuses them.
|
|
54
|
+
const selfRestart = restart;
|
|
55
|
+
const noRestart = { error: 'this identity runs inside its pod server, which cannot restart for it — front it, or un-front it, from a standalone agent' };
|
|
62
56
|
if (body.action === 'configure') {
|
|
63
57
|
if (!/^https:\/\/\S+$/.test(String(body.url || ''))) return json(res, 400, { error: 'gateway url must be https' });
|
|
64
58
|
if (!/^https?:\/\/\S+$/.test(String(body.webId || ''))) return json(res, 400, { error: 'gateway webId must be a URL' });
|
|
@@ -121,6 +115,7 @@ export async function post(p, body, ctx, req, res) { // eslint-disable-line no
|
|
|
121
115
|
if (agent.embedded) {
|
|
122
116
|
return json(res, 400, { error: 'this identity runs inside its pod server and has no portable credential — attach from a standalone agent' });
|
|
123
117
|
}
|
|
118
|
+
if (body.fronted === true && !selfRestart) return json(res, 400, noRestart);
|
|
124
119
|
const named = !!String(body.handle || '').trim();
|
|
125
120
|
let handle = String(body.handle || cfg.handle || '').toLowerCase().trim();
|
|
126
121
|
if (!handle) return json(res, 400, { error: 'a name at the gateway is required' });
|
|
@@ -177,6 +172,7 @@ export async function post(p, body, ctx, req, res) { // eslint-disable-line no
|
|
|
177
172
|
if (body.action === 'forget') {
|
|
178
173
|
const wasLocked = g.mode === 'locked';
|
|
179
174
|
const wasFronted = !!g.frontActor;
|
|
175
|
+
if (wasFronted && !selfRestart) return json(res, 400, noRestart);
|
|
180
176
|
delete cfg.gateway; agent.store.setConfig(cfg);
|
|
181
177
|
if (agent.publisher) agent.publisher.config.gateway = undefined;
|
|
182
178
|
await agent.store.flush();
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
// pruning the inbox, rebuilding posts, republishing, parking and reviving,
|
|
3
3
|
// rotating the key, moving, aliases, importing, retiring.
|
|
4
4
|
|
|
5
|
-
import { resolveHandle } from '
|
|
6
|
-
import { webfingerHost } from '
|
|
7
|
-
import { normalizeImport, IMPORT_KINDS } from '
|
|
5
|
+
import { resolveHandle } from '../../core/social.mjs';
|
|
6
|
+
import { webfingerHost } from '../../core/wire.mjs';
|
|
7
|
+
import { normalizeImport, IMPORT_KINDS } from '../../connections/import.mjs';
|
|
8
8
|
|
|
9
9
|
export async function post(p, body, ctx, req, res) { // eslint-disable-line no-unused-vars
|
|
10
10
|
const { agent, log, allowed, embedded, port, publicOrigin, versionOnDisk, isGroup } = ctx; // eslint-disable-line no-unused-vars
|
|
@@ -3,13 +3,11 @@
|
|
|
3
3
|
|
|
4
4
|
import fs from 'node:fs';
|
|
5
5
|
import path from 'node:path';
|
|
6
|
-
import { announceModeration } from '
|
|
7
|
-
import { addRemoveActivity, publicHandle } from '
|
|
8
|
-
import { hashPassword } from '
|
|
9
|
-
import {
|
|
10
|
-
import { localFetch } from '../../../client/localapi.mjs';
|
|
6
|
+
import { announceModeration } from '../../core/social.mjs';
|
|
7
|
+
import { addRemoveActivity, publicHandle } from '../../core/wire.mjs';
|
|
8
|
+
import { hashPassword } from '../../client/masto/index.mjs';
|
|
9
|
+
import { tildify } from '../../shared/files.mjs';
|
|
11
10
|
import { AGENT_VERSION } from '../static.mjs';
|
|
12
|
-
import { secureOrigin } from '../origins.mjs';
|
|
13
11
|
|
|
14
12
|
// The identity itself. Changing any of these means a different actor at a
|
|
15
13
|
// different address, which is a new setup, not an edit.
|
|
@@ -42,60 +40,6 @@ export async function get(p, ctx, req, res, url) { // eslint-disable-line no-u
|
|
|
42
40
|
stats: agent.store.read('gateway-stats.json', { verified: 0, unverified: 0, lastAt: null }),
|
|
43
41
|
});
|
|
44
42
|
}
|
|
45
|
-
// The other identities on this machine, so the page can link to each one.
|
|
46
|
-
// Their `agent.json` is the only file read — a port and a handle. A
|
|
47
|
-
// sibling's credential and keys are never opened; anything else shown
|
|
48
|
-
// here comes from that agent answering /status for itself.
|
|
49
|
-
if (req.method === 'GET' && p === '/profiles') {
|
|
50
|
-
const here = path.resolve(agent.home || '');
|
|
51
|
-
// Which identity a plain command means: the last one started. A
|
|
52
|
-
// different question from `current`, which is the page you are on.
|
|
53
|
-
const wasLast = defaultProfile(rootOf(here));
|
|
54
|
-
const rows = await Promise.all(identityHomes(rootOf(here)).map(async ({ name, dir }) => {
|
|
55
|
-
// agent.json holds a port AND a handle — setup records both, exactly
|
|
56
|
-
// so the named origin can be built before that agent has said a word.
|
|
57
|
-
let rec = {};
|
|
58
|
-
try { rec = JSON.parse(fs.readFileSync(path.join(dir, 'agent.json'), 'utf8')) || {}; } catch {}
|
|
59
|
-
const port = rec.port || null;
|
|
60
|
-
// A directory under profiles/ is not an identity until it holds a
|
|
61
|
-
// credential or has run somewhere. A half-finished setup leaves one
|
|
62
|
-
// behind, and listing it offers a page that cannot exist yet.
|
|
63
|
-
if (!port && !fs.existsSync(path.join(dir, 'credential.json'))) return null;
|
|
64
|
-
const current = path.resolve(dir) === here;
|
|
65
|
-
const live = current ? agent.status() : port
|
|
66
|
-
? await localFetch(agent.home, port, '/status', { timeout: 1200 })
|
|
67
|
-
.then(r => r.json()).catch(() => null)
|
|
68
|
-
: null;
|
|
69
|
-
// The handle is right here — spending it on the origin is the whole
|
|
70
|
-
// point of having asked that agent who it is. A stopped one never
|
|
71
|
-
// answers, so its recorded handle stands in: the link has to be named
|
|
72
|
-
// before you get there, or starting it lands you on the shared origin.
|
|
73
|
-
const handle = live?.handle || rec.handle || null;
|
|
74
|
-
const origin = port
|
|
75
|
-
? secureOrigin(handle, port)
|
|
76
|
-
: null;
|
|
77
|
-
// The fediverse address, assembled from the two things /status has:
|
|
78
|
-
// the handle, and the pod host its actor URL sits on.
|
|
79
|
-
let address = null;
|
|
80
|
-
if (live?.handle && live?.actor) {
|
|
81
|
-
try {
|
|
82
|
-
const front = live.actor.match(/\/u\/([^/]+)\/ap\/actor\/?$/)?.[1];
|
|
83
|
-
address = `${front || live.handle}@${new URL(live.actor).host}`;
|
|
84
|
-
} catch { /* not a URL yet */ }
|
|
85
|
-
}
|
|
86
|
-
return {
|
|
87
|
-
name, port, current,
|
|
88
|
-
admin: origin ? `${origin}/admin/` : null,
|
|
89
|
-
app: origin ? `${origin}/` : null,
|
|
90
|
-
handle,
|
|
91
|
-
address,
|
|
92
|
-
lastUsed: name === wasLast,
|
|
93
|
-
kind: live?.kind || null,
|
|
94
|
-
mode: live ? live.mode : null,
|
|
95
|
-
};
|
|
96
|
-
}));
|
|
97
|
-
return json(res, 200, { identities: rows.filter(Boolean) });
|
|
98
|
-
}
|
|
99
43
|
if (req.method === 'GET' && p === '/blocks') return json(res, 200, agent.store.getBlocklist());
|
|
100
44
|
if (req.method === 'GET' && p === '/log') return json(res, 200, { lines: agent.logLines(200) });
|
|
101
45
|
if (req.method === 'GET' && p === '/deadletter') return json(res, 200, { items: agent.store.getDeadLetters() });
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// moderating: posting, the tag feed, follows, mutes, blocks, ejecting,
|
|
3
3
|
// retracting, review, joins, the moderation queue.
|
|
4
4
|
|
|
5
|
-
import { followHandle, followActor, unfollowActor, ejectFollower, retractAnnouncement, admitRequest, refuseRequest, applyModeration } from '
|
|
5
|
+
import { followHandle, followActor, unfollowActor, ejectFollower, retractAnnouncement, admitRequest, refuseRequest, applyModeration } from '../../core/social.mjs';
|
|
6
6
|
|
|
7
7
|
export async function get(p, ctx, req, res, url) { // eslint-disable-line no-unused-vars
|
|
8
8
|
const { agent, log, allowed, embedded, port, publicOrigin, versionOnDisk, isGroup } = ctx; // eslint-disable-line no-unused-vars
|
|
@@ -10,7 +10,7 @@ import path from 'node:path';
|
|
|
10
10
|
import { fileURLToPath } from 'node:url';
|
|
11
11
|
import { wsOrigins } from './origins.mjs';
|
|
12
12
|
|
|
13
|
-
export const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '
|
|
13
|
+
export const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
|
|
14
14
|
|
|
15
15
|
const PHANPY_DIR = path.join(projectRoot, 'phanpy/dist');
|
|
16
16
|
// The vendored client is upstream's build, byte for byte, so the integrity
|
|
@@ -63,6 +63,11 @@ export const webMount = (pathname) => {
|
|
|
63
63
|
export const SETUP_PAGE = '/admin/setup/';
|
|
64
64
|
|
|
65
65
|
export const AGENT_VERSION = JSON.parse(fs.readFileSync(path.join(projectRoot, 'package.json'), 'utf8')).version;
|
|
66
|
+
// Read again on each ask: the checkout can move while a process stays up.
|
|
67
|
+
export const readVersionOnDisk = () => {
|
|
68
|
+
try { return JSON.parse(fs.readFileSync(path.join(projectRoot, 'package.json'), 'utf8')).version || null; }
|
|
69
|
+
catch { return null; }
|
|
70
|
+
};
|
|
66
71
|
|
|
67
72
|
const MIME = {
|
|
68
73
|
'.html': 'text/html; charset=utf-8', '.js': 'text/javascript', '.css': 'text/css',
|
|
@@ -8,17 +8,14 @@
|
|
|
8
8
|
|
|
9
9
|
import fs from 'node:fs';
|
|
10
10
|
import path from 'node:path';
|
|
11
|
-
import { MastoApi } from '
|
|
12
|
-
import { C2S } from '
|
|
13
|
-
import { makeC2sAuth } from '
|
|
14
|
-
import { Streaming } from '
|
|
15
|
-
import { nodeinfoPointer, nodeinfoDoc } from '
|
|
16
|
-
import { checkRequest } from '
|
|
17
|
-
import {
|
|
18
|
-
import { localVersion } from '../update.mjs';
|
|
19
|
-
import { projectRoot, AGENT_VERSION, SETUP_PAGE, securityHeaders, sendJson, serveStatic, serveWeb, webDirRedirect, webMount } from './static.mjs';
|
|
11
|
+
import { MastoApi } from '../client/masto/index.mjs';
|
|
12
|
+
import { C2S } from '../client/c2s.mjs';
|
|
13
|
+
import { makeC2sAuth } from '../client/oidc-auth.mjs';
|
|
14
|
+
import { Streaming } from '../client/streaming.mjs';
|
|
15
|
+
import { nodeinfoPointer, nodeinfoDoc } from '../core/wire.mjs';
|
|
16
|
+
import { checkRequest } from '../shared/guard.mjs';
|
|
17
|
+
import { projectRoot, AGENT_VERSION, SETUP_PAGE, readVersionOnDisk, securityHeaders, sendJson, serveStatic, serveWeb, webDirRedirect, webMount } from './static.mjs';
|
|
20
18
|
import * as owner from './routes/owner.mjs';
|
|
21
|
-
import * as setup from './routes/setup.mjs';
|
|
22
19
|
import * as lifecycle from './routes/lifecycle.mjs';
|
|
23
20
|
import * as gateway from './routes/gateway.mjs';
|
|
24
21
|
import * as social from './routes/social.mjs';
|
|
@@ -65,9 +62,8 @@ function readBody(req) {
|
|
|
65
62
|
});
|
|
66
63
|
}
|
|
67
64
|
|
|
68
|
-
//
|
|
69
|
-
|
|
70
|
-
const ROUTES = [owner, setup, lifecycle, gateway, social, connections];
|
|
65
|
+
// Whether this home has been set up: the credential FILE, never configured().
|
|
66
|
+
const hasCredential = (home) => fs.existsSync(path.join(home, 'credential.json'));
|
|
71
67
|
|
|
72
68
|
// The whole admin/facade surface as one request handler, with nothing that
|
|
73
69
|
// belongs to a process of its own. startAdmin wraps it in listeners; the CSS
|
|
@@ -83,10 +79,17 @@ const SESSION_FILES = {
|
|
|
83
79
|
'/oauth/session/oidc-session.mjs': 'lib/session/oidc-session.mjs',
|
|
84
80
|
};
|
|
85
81
|
|
|
82
|
+
// `extraRoutes` are the routes a host process adds after the owner's — the
|
|
83
|
+
// DeviceAgent's setup and process routes; a pod server adds none. `restart`
|
|
84
|
+
// restarts the host process, for the changes that need one; a host that
|
|
85
|
+
// cannot passes nothing, and those changes are refused.
|
|
86
86
|
export function buildAdminSurface({ agent, gate, allowed, log = console.log,
|
|
87
87
|
port = null, handle = null, embedded = false, basePath = '/', mount = '',
|
|
88
|
-
publicOrigin = null, scheme = null,
|
|
89
|
-
versionOnDisk =
|
|
88
|
+
publicOrigin = null, scheme = null, extraRoutes = [], restart = null,
|
|
89
|
+
versionOnDisk = readVersionOnDisk }) {
|
|
90
|
+
// The routes, in the order the one if-chain and the one switch used to be
|
|
91
|
+
// read: the first module that answers a path wins.
|
|
92
|
+
const ROUTES = [owner, ...extraRoutes, lifecycle, gateway, social, connections];
|
|
90
93
|
const json = (res, status, obj) => sendJson(res, status, obj, allowed);
|
|
91
94
|
const masto = new MastoApi({ agent, log, allowed, scheme, embedded, mount,
|
|
92
95
|
throughDoor: (req) => Boolean(gate?.upgradeOk?.(req)) });
|
|
@@ -123,7 +126,7 @@ export function buildAdminSurface({ agent, gate, allowed, log = console.log,
|
|
|
123
126
|
const atPath = (p_) => mount + (basePath === '/' ? p_ : basePath.slice(0, -1) + p_);
|
|
124
127
|
|
|
125
128
|
// What every route may reach: the agent and the deployment's facts.
|
|
126
|
-
const ctx = { agent, log, allowed, embedded, port, handle, publicOrigin, versionOnDisk, isGroup, json, setup: setup_ };
|
|
129
|
+
const ctx = { agent, log, allowed, embedded, port, handle, publicOrigin, versionOnDisk, isGroup, json, setup: setup_, restart };
|
|
127
130
|
|
|
128
131
|
const handler = async (req, res) => {
|
|
129
132
|
const url = new URL(req.url, 'http://localhost');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fedipod",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.43.0",
|
|
4
4
|
"description": "Standalone single-actor ActivityPub agent whose wire face, RDF truth and state all live on a Solid pod (CSS). Bundles a Phanpy UI and a Mastodon client-API facade.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -14,6 +14,24 @@
|
|
|
14
14
|
"bin": {
|
|
15
15
|
"fedipod": "bin/fedipod.mjs"
|
|
16
16
|
},
|
|
17
|
+
"exports": {
|
|
18
|
+
"./embed": "./lib/server/embed.mjs",
|
|
19
|
+
"./front": "./lib/gateway/front-core.mjs",
|
|
20
|
+
"./front-pages": "./lib/gateway/front-pages.mjs",
|
|
21
|
+
"./surface": "./lib/surface/index.mjs",
|
|
22
|
+
"./agent": "./lib/core/agent.mjs",
|
|
23
|
+
"./remote": "./lib/core/remote.mjs",
|
|
24
|
+
"./place": "./lib/core/place.mjs",
|
|
25
|
+
"./core/*": "./lib/core/*",
|
|
26
|
+
"./pod/*": "./lib/pod/*",
|
|
27
|
+
"./client/*": "./lib/client/*",
|
|
28
|
+
"./connections/*": "./lib/connections/*",
|
|
29
|
+
"./gateway/*": "./lib/gateway/*",
|
|
30
|
+
"./shared/*": "./lib/shared/*",
|
|
31
|
+
"./surface/*": "./lib/surface/*",
|
|
32
|
+
"./vendor/*": "./vendor/*",
|
|
33
|
+
"./package.json": "./package.json"
|
|
34
|
+
},
|
|
17
35
|
"files": [
|
|
18
36
|
"bin",
|
|
19
37
|
"lib",
|
|
@@ -30,22 +48,22 @@
|
|
|
30
48
|
"architecture.svg",
|
|
31
49
|
"device-agent.md",
|
|
32
50
|
"architecture.md",
|
|
33
|
-
"gateway.md"
|
|
34
|
-
"browser.svg"
|
|
51
|
+
"gateway.md"
|
|
35
52
|
],
|
|
36
53
|
"scripts": {
|
|
37
54
|
"start": "node bin/fedipod.mjs up",
|
|
38
55
|
"install-service": "node bin/fedipod.mjs install-service",
|
|
39
56
|
"uninstall-service": "node bin/fedipod.mjs uninstall-service",
|
|
40
57
|
"prepublishOnly": "npm test",
|
|
41
|
-
"test": "node claude/smoke-tests/agent-smoke.mjs && node claude/smoke-tests/front-hard-test.mjs && node claude/smoke-tests/copy-test.mjs && node claude/smoke-tests/masto-gateway-test.mjs && node scripts/check-pod-calls.mjs && node scripts/verify-ui.mjs && node --test packages/fedipod-bb/test/*.test.mjs && node scripts/check-sizes.mjs",
|
|
58
|
+
"test": "node claude/smoke-tests/agent-smoke.mjs && node claude/smoke-tests/front-hard-test.mjs && node claude/smoke-tests/copy-test.mjs && node claude/smoke-tests/copy-mode-test.mjs && node claude/smoke-tests/masto-gateway-test.mjs && node scripts/check-pod-calls.mjs && node scripts/verify-ui.mjs && node packages/fedipod-bb/scripts/link-fedipod.mjs && node --test packages/fedipod-bb/test/*.test.mjs && node scripts/check-sizes.mjs",
|
|
42
59
|
"verify-ui": "node scripts/verify-ui.mjs",
|
|
43
60
|
"build:sw": "node scripts/build-app.mjs --entry web/app/sw-src.mjs --out web/app/dist/sw.js",
|
|
44
61
|
"build:boot": "node scripts/build-app.mjs --entry web/app/boot.mjs --out web/app/dist/boot.js",
|
|
45
62
|
"build:app": "npm run build:sw && npm run build:boot",
|
|
46
63
|
"stage": "node scripts/stage-site.mjs",
|
|
47
64
|
"audit": "npm audit --omit=dev",
|
|
48
|
-
"agent": "node run-agent.mjs"
|
|
65
|
+
"agent": "node run-agent.mjs",
|
|
66
|
+
"vendor:sanitize": "node scripts/vendor-sanitize.mjs"
|
|
49
67
|
},
|
|
50
68
|
"dependencies": {
|
|
51
69
|
"@fedify/fedify": "^2.3.4",
|