fedipod 1.40.0 → 1.42.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/gateway-move.mjs +58 -0
- 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 +7 -1
- package/lib/gateway/front-core.mjs +6 -16
- package/lib/gateway/front-pages.mjs +31 -0
- package/lib/gateway/keeper-session.mjs +10 -1
- 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 +18 -714
- package/scripts/build-app.mjs +4 -0
- package/scripts/check-netlify-bundles.mjs +84 -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
|
@@ -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.42.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 --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",
|