fedipod-server 0.11.0 → 0.13.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 +22 -6
- package/dist/claims.d.ts +8 -0
- package/dist/claims.js +10 -0
- package/dist/handler.d.ts +13 -0
- package/dist/handler.js +56 -11
- package/dist/handler.jsonld +8 -0
- package/dist/store-pod.js +18 -4
- package/lib/{c2s.mjs → client/c2s.mjs} +10 -3
- package/lib/{localapi.mjs → client/localapi.mjs} +2 -2
- package/lib/client/masto/accounts.mjs +264 -0
- package/lib/client/masto/body.mjs +69 -0
- package/lib/client/masto/index.mjs +183 -0
- package/lib/client/masto/instance.mjs +104 -0
- package/lib/client/masto/media.mjs +133 -0
- package/lib/client/masto/oauth.mjs +599 -0
- package/lib/client/masto/render.mjs +459 -0
- package/lib/client/masto/statuses.mjs +331 -0
- package/lib/client/masto/timelines.mjs +316 -0
- package/lib/{streaming.mjs → client/streaming.mjs} +1 -1
- package/lib/{acctfeed.mjs → connections/acctfeed.mjs} +1 -1
- package/lib/{atproto.mjs → connections/atproto.mjs} +15 -16
- package/lib/{bskygroup.mjs → connections/bskygroup.mjs} +1 -1
- package/lib/{fediacct.mjs → connections/fediacct.mjs} +31 -35
- package/lib/{import.mjs → connections/import.mjs} +1 -1
- package/lib/{tagfeed.mjs → connections/tagfeed.mjs} +3 -3
- package/lib/connections/vault.mjs +114 -0
- package/lib/core/as2.mjs +170 -0
- package/lib/core/contexts/activitystreams.json +379 -0
- package/lib/core/contexts/did-v1.json +57 -0
- package/lib/core/contexts/fep-5711.json +36 -0
- package/lib/core/contexts/gotosocial.json +86 -0
- package/lib/core/contexts/identity-v1.json +152 -0
- package/lib/core/contexts/index.mjs +45 -0
- package/lib/core/contexts/join-lemmy.json +33 -0
- package/lib/core/contexts/joinmastodon.json +28 -0
- package/lib/core/contexts/map.json +16 -0
- package/lib/core/contexts/miscellany.json +19 -0
- package/lib/core/contexts/schemaorg.json +8845 -0
- package/lib/core/contexts/security-data-integrity-v1.json +78 -0
- package/lib/core/contexts/security-data-integrity-v2.json +81 -0
- package/lib/core/contexts/security-multikey-v1.json +35 -0
- package/lib/core/contexts/security-v1.json +74 -0
- package/lib/core/contexts/webfinger.json +10 -0
- package/lib/{deliver.mjs → core/deliver.mjs} +2 -2
- package/lib/core/graphview.mjs +269 -0
- package/lib/core/intake/activities.mjs +437 -0
- package/lib/core/intake/activity.mjs +240 -0
- package/lib/core/intake/channel.mjs +144 -0
- package/lib/core/intake/group.mjs +222 -0
- package/lib/core/intake/index.mjs +629 -0
- package/lib/core/intake/notes.mjs +288 -0
- package/lib/core/intake/verify.mjs +142 -0
- package/lib/{keys.mjs → core/keys.mjs} +1 -1
- package/lib/core/publisher/collections.mjs +229 -0
- package/lib/core/publisher/index.mjs +421 -0
- package/lib/core/publisher/notes.mjs +188 -0
- package/lib/core/publisher/questions.mjs +233 -0
- package/lib/core/publisher/restore.mjs +199 -0
- package/lib/core/shapes/activitystreams.ttl +129 -0
- package/lib/core/shapes/index.mjs +107 -0
- package/lib/core/shapes/shapes-text.mjs +13 -0
- package/lib/{social.mjs → core/social.mjs} +2 -2
- package/lib/{store.mjs → core/store.mjs} +4 -0
- package/lib/{wire.mjs → core/wire.mjs} +2 -2
- package/lib/device/admin/index.mjs +13 -0
- package/lib/device/admin/origins.mjs +35 -0
- package/lib/device/admin/routes/connections.mjs +144 -0
- package/lib/device/admin/routes/gateway.mjs +199 -0
- package/lib/device/admin/routes/lifecycle.mjs +191 -0
- package/lib/device/admin/routes/owner.mjs +322 -0
- package/lib/device/admin/routes/setup.mjs +393 -0
- package/lib/device/admin/routes/social.mjs +188 -0
- package/lib/device/admin/server.mjs +95 -0
- package/lib/device/admin/static.mjs +244 -0
- package/lib/device/admin/surface.mjs +274 -0
- package/lib/device/cli/commands/account.mjs +586 -0
- package/lib/device/cli/commands/run.mjs +278 -0
- package/lib/device/cli/commands/service.mjs +221 -0
- package/lib/device/cli/commands/setup.mjs +410 -0
- package/lib/device/cli/commands/state.mjs +559 -0
- package/lib/device/cli/context.mjs +288 -0
- package/lib/{migrate.mjs → device/migrate.mjs} +1 -1
- package/lib/{remote.mjs → device/remote.mjs} +3 -3
- package/lib/{setup.mjs → device/setup.mjs} +3 -3
- package/lib/{update.mjs → device/update.mjs} +1 -1
- package/lib/{directory.mjs → gateway/directory.mjs} +1 -1
- package/lib/{front-core.mjs → gateway/front-core.mjs} +3 -3
- package/lib/{gateway-core.mjs → gateway/gateway-core.mjs} +1 -1
- package/lib/{httpsig.mjs → gateway/httpsig.mjs} +1 -1
- package/lib/server/embed.mjs +405 -0
- package/lib/{links.mjs → shared/links.mjs} +1 -1
- package/lib/{ua.mjs → shared/ua.mjs} +1 -1
- package/package.json +1 -1
- package/run-agent.mjs +33 -25
- package/web/admin/actors.js +145 -0
- package/web/admin/common.js +23 -0
- package/web/admin/connections.js +112 -0
- package/web/admin/gateway.js +111 -0
- package/web/admin/group.js +258 -0
- package/web/admin/index.html +7 -1
- package/web/admin/record.js +378 -0
- package/web/admin/setup/index.html +1 -0
- package/web/admin/setup/setup.js +2 -13
- package/web/admin/upkeep.js +170 -0
- package/web/app/README.md +6 -6
- package/web/app/admin-facade.mjs +3 -3
- package/web/app/agent.mjs +14 -16
- package/web/app/atproto-browser.mjs +1 -1
- package/web/app/boot.mjs +2 -3
- package/web/app/deliver-relay.mjs +1 -1
- package/web/app/dist/boot.js +22 -3
- package/web/app/dist/boot.js.map +2 -2
- package/web/app/dist/sw.js +21913 -5446
- package/web/app/dist/sw.js.map +4 -4
- package/web/app/fediacct-browser.mjs +1 -1
- package/web/app/keys-browser.mjs +27 -4
- package/web/app/shims/shapes-text.mjs +8 -0
- package/web/app/signup.mjs +2 -3
- package/web/app/site/admin/actors.js +145 -0
- package/web/app/site/admin/common.js +23 -0
- package/web/app/site/admin/connections.js +112 -0
- package/web/app/site/admin/gateway.js +111 -0
- package/web/app/site/admin/group.js +258 -0
- package/web/app/site/admin/index.html +7 -1
- package/web/app/site/admin/record.js +378 -0
- package/web/app/site/admin/setup/index.html +1 -0
- package/web/app/site/admin/setup/setup.js +2 -13
- package/web/app/site/admin/upkeep.js +170 -0
- package/web/app/site/boot.js +22 -3
- package/web/app/site/sw.js +21913 -5446
- package/web/app/sw-src.mjs +17 -2
- package/lib/admin.mjs +0 -1913
- package/lib/embed.mjs +0 -220
- package/lib/intake.mjs +0 -1981
- package/lib/mastoapi.mjs +0 -2284
- package/lib/publisher.mjs +0 -1192
- package/web/admin/admin.js +0 -1181
- package/web/app/site/admin/admin.js +0 -1181
- /package/lib/{oidc-auth.mjs → client/oidc-auth.mjs} +0 -0
- /package/lib/{webpush.mjs → client/webpush.mjs} +0 -0
- /package/lib/{bskyfeed.mjs → connections/bskyfeed.mjs} +0 -0
- /package/lib/{lease.mjs → core/lease.mjs} +0 -0
- /package/lib/{polls.mjs → core/polls.mjs} +0 -0
- /package/lib/{proof.mjs → core/proof.mjs} +0 -0
- /package/lib/{storage.mjs → core/storage.mjs} +0 -0
- /package/lib/{account.mjs → device/account.mjs} +0 -0
- /package/lib/{certs.mjs → device/certs.mjs} +0 -0
- /package/lib/{export-collections.mjs → device/export-collections.mjs} +0 -0
- /package/lib/{home.mjs → device/home.mjs} +0 -0
- /package/lib/{ports.mjs → device/ports.mjs} +0 -0
- /package/lib/{guard.mjs → shared/guard.mjs} +0 -0
- /package/lib/{safefetch.mjs → shared/safefetch.mjs} +0 -0
package/lib/embed.mjs
DELETED
|
@@ -1,220 +0,0 @@
|
|
|
1
|
-
// embed.mjs — the agent, running inside the pod server that holds its pod.
|
|
2
|
-
//
|
|
3
|
-
// Nothing here reimplements the agent: it is the same Agent class, the same
|
|
4
|
-
// intake, deliverer, publisher and lease. What changes is the transport. The
|
|
5
|
-
// caller hands in a session that reaches the pod through the server's own
|
|
6
|
-
// store, and RemotePod takes it instead of minting a credential — so an
|
|
7
|
-
// identity acts on its pod with no token, no socket, and no second process.
|
|
8
|
-
//
|
|
9
|
-
// The one thing a pod server can do that a laptop cannot is notice a write the
|
|
10
|
-
// moment it happens, so the notification socket is replaced by the store's own
|
|
11
|
-
// change events.
|
|
12
|
-
|
|
13
|
-
import crypto from 'node:crypto';
|
|
14
|
-
import fs from 'node:fs';
|
|
15
|
-
import path from 'node:path';
|
|
16
|
-
|
|
17
|
-
import { createRequire } from 'node:module';
|
|
18
|
-
|
|
19
|
-
import { Agent } from '../run-agent.mjs';
|
|
20
|
-
import { RemotePod } from './remote.mjs';
|
|
21
|
-
import { apUrls } from './wire.mjs';
|
|
22
|
-
import { writeJsonAtomic } from './home.mjs';
|
|
23
|
-
import { buildAdminSurface } from './admin.mjs';
|
|
24
|
-
import { FixedAuthorities } from './guard.mjs';
|
|
25
|
-
|
|
26
|
-
const require = createRequire(import.meta.url);
|
|
27
|
-
const { makeGate } = require('../vendor/gate.cjs');
|
|
28
|
-
|
|
29
|
-
// How long to gather store events before draining, so a delivery of several
|
|
30
|
-
// items costs one sweep rather than one each.
|
|
31
|
-
const DRAIN_COALESCE_MS = 250;
|
|
32
|
-
|
|
33
|
-
// How every secret in this project is minted (the attach flow's recipe).
|
|
34
|
-
const mintSecret = () => crypto.randomBytes(32).toString('base64');
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* The secret guarding one identity's owner door, kept beside its signing key.
|
|
38
|
-
* Minted when absent; `rotate` re-mints over an existing one — which is how a
|
|
39
|
-
* lost secret is recovered, by proving pod control again. Never logged.
|
|
40
|
-
*/
|
|
41
|
-
export function ensureDoorSecret(dataDir, handle, { rotate = false } = {}) {
|
|
42
|
-
const home = path.join(dataDir, handle);
|
|
43
|
-
fs.mkdirSync(home, { recursive: true, mode: 0o700 });
|
|
44
|
-
const file = path.join(home, 'door-secret.json');
|
|
45
|
-
if (!rotate) {
|
|
46
|
-
try {
|
|
47
|
-
const rec = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
48
|
-
if (rec?.secret) return { secret: rec.secret, path: file, rotated: false };
|
|
49
|
-
} catch { /* absent or unreadable: mint below */ }
|
|
50
|
-
}
|
|
51
|
-
const rec = { secret: mintSecret(), mintedAt: new Date().toISOString() };
|
|
52
|
-
writeJsonAtomic(file, rec, { mode: 0o600 });
|
|
53
|
-
return { secret: rec.secret, path: file, rotated: rotate };
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/** The identity's name: a subdomain pod is its label, a path pod its last segment. */
|
|
57
|
-
export function handleFor(podBase) {
|
|
58
|
-
const u = new URL(podBase);
|
|
59
|
-
const segments = u.pathname.split('/').filter(Boolean);
|
|
60
|
-
return segments.length ? segments[segments.length - 1] : u.hostname.split('.')[0];
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* The credential file an embedded identity runs on. It names the pod and
|
|
65
|
-
* nothing else: there is no client id and no secret, because there is nobody
|
|
66
|
-
* to authenticate to. Absent `privateRoot`, so the state tree stays on the pod.
|
|
67
|
-
*/
|
|
68
|
-
function ensureCredential(home, { podBase, webId }) {
|
|
69
|
-
const file = path.join(home, 'credential.json');
|
|
70
|
-
try { return JSON.parse(fs.readFileSync(file, 'utf8')); }
|
|
71
|
-
catch { /* first run for this identity */ }
|
|
72
|
-
const rec = {
|
|
73
|
-
webId,
|
|
74
|
-
remotePod: podBase.endsWith('/') ? podBase : podBase + '/',
|
|
75
|
-
root: 'activitypods-js/',
|
|
76
|
-
};
|
|
77
|
-
writeJsonAtomic(file, rec, { mode: 0o600 });
|
|
78
|
-
return rec;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Bring one identity up inside the server.
|
|
83
|
-
*
|
|
84
|
-
* `session` is the store-backed transport ({ fetch, warmup, stats }).
|
|
85
|
-
* `resourceStore` is optional and used only to watch for inbox writes.
|
|
86
|
-
* Returns the running agent and a stop() that leaves the pod tidy.
|
|
87
|
-
*/
|
|
88
|
-
export async function startEmbeddedAgent({
|
|
89
|
-
podBase,
|
|
90
|
-
dataDir,
|
|
91
|
-
session,
|
|
92
|
-
resourceStore = null,
|
|
93
|
-
webIdSuffix = 'profile/card#me',
|
|
94
|
-
log = () => {},
|
|
95
|
-
pollSeconds = null,
|
|
96
|
-
autoAcceptFollows = true,
|
|
97
|
-
gateToken = null,
|
|
98
|
-
uiPath = '/fedipod/',
|
|
99
|
-
}) {
|
|
100
|
-
const base = podBase.endsWith('/') ? podBase : podBase + '/';
|
|
101
|
-
const handle = handleFor(base);
|
|
102
|
-
const home = path.join(dataDir, handle);
|
|
103
|
-
fs.mkdirSync(home, { recursive: true, mode: 0o700 });
|
|
104
|
-
|
|
105
|
-
const webId = base + webIdSuffix.replace(/^\//u, '');
|
|
106
|
-
const cred = ensureCredential(home, { podBase: base, webId });
|
|
107
|
-
|
|
108
|
-
const agent = new Agent({ home, log });
|
|
109
|
-
agent.log = log;
|
|
110
|
-
agent.store.log = log;
|
|
111
|
-
// Read by connect() when it builds Intake: in-process, the pod's own change
|
|
112
|
-
// events are the push channel.
|
|
113
|
-
agent.embedded = true;
|
|
114
|
-
if (pollSeconds) agent.pollSeconds = pollSeconds;
|
|
115
|
-
agent.remote = new RemotePod(cred, { log, session });
|
|
116
|
-
await agent.remote.warmup();
|
|
117
|
-
|
|
118
|
-
// Whether this pod already carries an identity is the question connect()
|
|
119
|
-
// assumes an answer to, so settle it first.
|
|
120
|
-
const urls = apUrls(cred.remotePod, cred.root);
|
|
121
|
-
agent.store.attach(agent.privateStorage(cred, 'state', urls));
|
|
122
|
-
|
|
123
|
-
// A state container that is NOT THERE means a pod nobody has set up yet. A
|
|
124
|
-
// state container we could not READ means a pod we could not ask, which is a
|
|
125
|
-
// different answer: treating it as empty would provision a second identity
|
|
126
|
-
// over the top of a working one.
|
|
127
|
-
const state = await agent.remote.fetch(urls.state, { headers: { accept: 'text/turtle' } });
|
|
128
|
-
const unprovisioned = state.status === 404 || state.status === 410;
|
|
129
|
-
if (!unprovisioned && state.status >= 400) {
|
|
130
|
-
throw new Error(`state at ${urls.state} → ${state.status} — cannot tell whether this pod is set up`);
|
|
131
|
-
}
|
|
132
|
-
if (!unprovisioned) {
|
|
133
|
-
await agent.store.load();
|
|
134
|
-
agent.stateLoaded = true;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
if (!agent.store.getConfig()) {
|
|
138
|
-
// The pod itself must exist first. A server creates its seeded pods after
|
|
139
|
-
// it runs its initializers, so an agent that provisioned eagerly would
|
|
140
|
-
// leave a container tree exactly where a pod is about to be created.
|
|
141
|
-
const profile = await agent.remote.fetch(webId.split('#')[0], { headers: { accept: 'text/turtle' } });
|
|
142
|
-
if (profile.status === 404 || profile.status === 410) {
|
|
143
|
-
throw new Error(`no pod at ${base} yet — its owner profile is not there`);
|
|
144
|
-
}
|
|
145
|
-
log(`no identity on ${base} yet — provisioning @${handle}`);
|
|
146
|
-
await agent.bootstrap({ handle, name: handle, kind: 'person' });
|
|
147
|
-
if (autoAcceptFollows) {
|
|
148
|
-
agent.store.setConfig({ ...agent.store.getConfig(), autoAcceptFollows: true });
|
|
149
|
-
await agent.store.flush();
|
|
150
|
-
}
|
|
151
|
-
agent.stateLoaded = true;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
await agent.connect();
|
|
155
|
-
|
|
156
|
-
// The client surfaces, on the pod's own origin: the Mastodon API a phone app
|
|
157
|
-
// speaks, the write API, nodeinfo, and behind the door the admin routes and
|
|
158
|
-
// the web client. Same code the standalone agent serves, minus the routes
|
|
159
|
-
// that only mean something to a process of one's own.
|
|
160
|
-
const authorities = new FixedAuthorities(base);
|
|
161
|
-
agent.authorities = authorities;
|
|
162
|
-
const surface = buildAdminSurface({
|
|
163
|
-
agent,
|
|
164
|
-
log,
|
|
165
|
-
gate: makeGate(gateToken, { secureCookie: authorities.secure }),
|
|
166
|
-
allowed: authorities,
|
|
167
|
-
embedded: true,
|
|
168
|
-
basePath: uiPath,
|
|
169
|
-
publicOrigin: base,
|
|
170
|
-
scheme: new URL(base).protocol,
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
// A delivery landing in the inbox — through the door, or from any other
|
|
174
|
-
// writer — wakes the drain at once. Only additions, and only in the inbox:
|
|
175
|
-
// the drain's own DELETEs must not call it back.
|
|
176
|
-
let coalesce = null;
|
|
177
|
-
let onChanged = null;
|
|
178
|
-
if (typeof resourceStore?.on === 'function') {
|
|
179
|
-
const inbox = urls.inbox;
|
|
180
|
-
onChanged = (identifier, activity) => {
|
|
181
|
-
const kind = String(activity?.value ?? activity ?? '');
|
|
182
|
-
if (!identifier?.path?.startsWith(inbox)) return;
|
|
183
|
-
if (!kind.endsWith('Create') && !kind.endsWith('Add')) return;
|
|
184
|
-
if (coalesce) return;
|
|
185
|
-
coalesce = setTimeout(() => {
|
|
186
|
-
coalesce = null;
|
|
187
|
-
if (!agent.viewer) agent.intake?.drain().catch((e) => log(`drain: ${e.message}`));
|
|
188
|
-
}, DRAIN_COALESCE_MS);
|
|
189
|
-
coalesce.unref?.();
|
|
190
|
-
};
|
|
191
|
-
resourceStore.on('changed', onChanged);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
const stop = async () => {
|
|
195
|
-
if (onChanged) resourceStore.off('changed', onChanged);
|
|
196
|
-
surface.streaming?.stop?.();
|
|
197
|
-
clearTimeout(coalesce);
|
|
198
|
-
agent.intake?.stop();
|
|
199
|
-
agent.tagfeed?.stop();
|
|
200
|
-
agent.bskyfeed?.stop();
|
|
201
|
-
agent.deliverer?.stop();
|
|
202
|
-
agent.importer?.stop();
|
|
203
|
-
clearInterval(agent.schedTimer);
|
|
204
|
-
clearInterval(agent.refreshTimer);
|
|
205
|
-
agent.publisher?.stopPolls();
|
|
206
|
-
// Same order the standalone agent's shutdown uses: write what is pending,
|
|
207
|
-
// then let go of the lease so the next agent need not wait out the TTL.
|
|
208
|
-
await Promise.allSettled([
|
|
209
|
-
agent.store.flush(),
|
|
210
|
-
agent.viewer ? Promise.resolve() : agent.lease?.release(),
|
|
211
|
-
]);
|
|
212
|
-
};
|
|
213
|
-
|
|
214
|
-
// podHome and actorUrl are the identity's own locations on the pod. They are
|
|
215
|
-
// returned rather than rebuilt by the caller so the root name lives here.
|
|
216
|
-
return {
|
|
217
|
-
agent, handle, home, surface, host: authorities.host,
|
|
218
|
-
podHome: urls.home, actorUrl: urls.actor, stop,
|
|
219
|
-
};
|
|
220
|
-
}
|