fedipod-server 0.11.0 → 0.12.1
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 +15 -6
- package/dist/handler.d.ts +6 -0
- package/dist/handler.js +23 -10
- package/dist/handler.jsonld +4 -0
- package/dist/store-pod.js +18 -4
- package/lib/{c2s.mjs → client/c2s.mjs} +8 -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 +124 -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/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 +141 -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 +196 -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/{embed.mjs → server/embed.mjs} +149 -22
- 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 +12 -12
- package/web/app/atproto-browser.mjs +1 -1
- package/web/app/deliver-relay.mjs +1 -1
- package/web/app/dist/sw.js +21684 -5415
- package/web/app/dist/sw.js.map +4 -4
- package/web/app/fediacct-browser.mjs +1 -1
- package/web/app/shims/shapes-text.mjs +8 -0
- 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/sw.js +21684 -5415
- package/web/app/sw-src.mjs +17 -2
- package/lib/admin.mjs +0 -1913
- 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/web/admin/setup/setup.js
CHANGED
|
@@ -3,26 +3,15 @@
|
|
|
3
3
|
// at localhost:<port> or at <handle>.localhost:<port>.
|
|
4
4
|
//
|
|
5
5
|
// External file, not an inline script: the CSP allows 'self' plus the hashes
|
|
6
|
-
// of Phanpy's own inline bootstrap, and nothing else.
|
|
6
|
+
// of Phanpy's own inline bootstrap, and nothing else. `$`, `BASE`, `api` and
|
|
7
|
+
// `postJson` come from ../common.js, loaded first.
|
|
7
8
|
|
|
8
|
-
const $ = (id) => document.getElementById(id);
|
|
9
9
|
// Empty means gone, not a blank line where a sentence used to be.
|
|
10
10
|
const strap = (t) => { const el = $('strap'); el.textContent = t || ''; el.hidden = !t; };
|
|
11
11
|
// By id, not by position: these were `body > section` until a wrapper went
|
|
12
12
|
// round them for layout, and the selector then matched nothing — every pane
|
|
13
13
|
// kept whatever it started as and the page sat on "Reading this agent's state".
|
|
14
14
|
const show = (id) => { for (const s of document.querySelectorAll('section[id^="pane-"]')) s.hidden = s.id !== id; };
|
|
15
|
-
// The door this page is served behind: nothing when the agent runs on this
|
|
16
|
-
// machine, `/app` when the identity is hosted by its own pod server. Read from
|
|
17
|
-
// the page's own address, so one build serves both.
|
|
18
|
-
const BASE = location.pathname.replace(/\/admin\/.*$/u, '');
|
|
19
|
-
const api = async (path, init) => {
|
|
20
|
-
const res = await fetch(BASE + path, { ...init, headers: { ...(init?.headers || {}), 'x-fedipod-page': '1' } });
|
|
21
|
-
return { status: res.status, json: await res.json().catch(() => null) };
|
|
22
|
-
};
|
|
23
|
-
const postJson = (path, body) => api(path, {
|
|
24
|
-
method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body),
|
|
25
|
-
});
|
|
26
15
|
|
|
27
16
|
let state = null;
|
|
28
17
|
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
// upkeep.js — upkeep and the lifecycle: the output pane, drain, recover,
|
|
2
|
+
// log and dead letters; the confirmations for rotate, retire and move; and
|
|
3
|
+
// the inbox backlog panel.
|
|
4
|
+
|
|
5
|
+
// Which button filled the output pane, so clicking that one again closes it
|
|
6
|
+
// rather than re-fetching the same thing under an already-open panel.
|
|
7
|
+
let outputSource = null;
|
|
8
|
+
|
|
9
|
+
const OUTPUT_TITLES = { log: 'Log', deadletter: 'Dead letters', drain: 'Inbox drain', rebuild: 'Recovered posts' };
|
|
10
|
+
const output = (obj, source = null) => {
|
|
11
|
+
closePanels('output');
|
|
12
|
+
outputSource = source;
|
|
13
|
+
solWindow.show('output', OUTPUT_TITLES[source] || 'Output');
|
|
14
|
+
$('output').textContent = typeof obj === 'string' ? obj : JSON.stringify(obj, null, 2);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const showingFrom = (source) => outputSource === source && solWindow.openId() === 'output';
|
|
18
|
+
|
|
19
|
+
// Drain is the one that does work rather than reveals it, so a second click
|
|
20
|
+
// re-runs instead of closing. It can take a while — the agent joins a sweep
|
|
21
|
+
// already in flight and queues another after it — and a button that looks dead
|
|
22
|
+
// for a minute reads as broken.
|
|
23
|
+
$('do-drain').addEventListener('click', async (ev) => {
|
|
24
|
+
const b = ev.currentTarget;
|
|
25
|
+
b.disabled = true;
|
|
26
|
+
b.textContent = 'draining…';
|
|
27
|
+
say('draining the inbox — if a sweep is already running this waits for it to finish');
|
|
28
|
+
const r = await write('/drain', {}, 'inbox drained');
|
|
29
|
+
b.disabled = false;
|
|
30
|
+
b.textContent = 'Drain the inbox';
|
|
31
|
+
if (r) {
|
|
32
|
+
const box = r.inbox || {};
|
|
33
|
+
say(`inbox drained — ${box.count ?? 0} still waiting`);
|
|
34
|
+
output(r, 'drain');
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Like the drain, this does work rather than reveals it, so a second click
|
|
39
|
+
// re-runs. Nothing here can lose anything: it only adds posts back.
|
|
40
|
+
$('do-rebuild').addEventListener('click', async (ev) => {
|
|
41
|
+
const b = ev.currentTarget;
|
|
42
|
+
b.disabled = true;
|
|
43
|
+
b.textContent = 'recovering…';
|
|
44
|
+
say('reading what the pod still holds — one request per post, so this takes a moment');
|
|
45
|
+
const r = await write('/rebuild', {}, 'checked');
|
|
46
|
+
b.disabled = false;
|
|
47
|
+
b.textContent = 'Recover posts';
|
|
48
|
+
if (!r) return;
|
|
49
|
+
if (r.why) { say(r.why, 'err'); return; }
|
|
50
|
+
if (!r.landed) { say('recovered posts could NOT be saved — see the log', 'err'); return; }
|
|
51
|
+
say(r.recovered
|
|
52
|
+
? `recovered ${r.recovered} post(s) the pod had and this machine did not`
|
|
53
|
+
: `nothing was missing — the pod indexed ${r.indexed} post(s), all of them already here`);
|
|
54
|
+
output(r, 'rebuild');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
$('do-log').addEventListener('click', async () => {
|
|
58
|
+
if (showingFrom('log')) { closePanels(); return; }
|
|
59
|
+
const { json } = await api('/log');
|
|
60
|
+
output((json?.lines || []).slice(-60).join('\n') || 'nothing logged yet', 'log');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
$('do-deadletter').addEventListener('click', async () => {
|
|
64
|
+
if (showingFrom('deadletter')) { closePanels(); return; }
|
|
65
|
+
const { json } = await api('/deadletter');
|
|
66
|
+
output(json?.items?.length ? json.items : 'no dead letters', 'deadletter');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// ---- lifecycle ----
|
|
70
|
+
// None of these can be taken back by clicking again, so the button only opens
|
|
71
|
+
// the matching warning in the markup; the second click is the one that acts.
|
|
72
|
+
// Retire also wants the handle typed, because a misclick cannot produce it.
|
|
73
|
+
|
|
74
|
+
const LIFECYCLE = {
|
|
75
|
+
'rotate-key': { path: '/rotate-key', title: 'Rotate the signing key', done: (r) => (r.changed ? 'rotated and republished' : 'no change — the key was already fresh') },
|
|
76
|
+
retire: { path: '/retire', title: 'Retire this identity', go: 'Retire it', danger: true, done: (r) => `retired ${r.deletedAt}: Delete delivered to ${r.inboxes} inbox(es)` },
|
|
77
|
+
move: { path: '/move', title: 'Transfer this account away', go: 'Transfer it', focus: 'move-target',
|
|
78
|
+
done: (r) => `transferred to ${r.target}: Move delivered to ${r.inboxes} inbox(es), unfollowed ${r.unfollowed}/${r.following}` },
|
|
79
|
+
};
|
|
80
|
+
let pending = null;
|
|
81
|
+
|
|
82
|
+
const closeConfirm = () => closePanels();
|
|
83
|
+
|
|
84
|
+
for (const btn of document.querySelectorAll('[data-confirm]')) {
|
|
85
|
+
btn.addEventListener('click', () => {
|
|
86
|
+
const what = btn.dataset.confirm;
|
|
87
|
+
// The same button again closes its question rather than re-asking it.
|
|
88
|
+
if (pending === what && solWindow.openId() === 'confirm-form') { closePanels(); return; }
|
|
89
|
+
closePanels(); // including whatever else was open
|
|
90
|
+
pending = what;
|
|
91
|
+
const spec = LIFECYCLE[what];
|
|
92
|
+
$(`warn-${what}`).hidden = false;
|
|
93
|
+
solWindow.show('confirm-form', spec.title);
|
|
94
|
+
$('confirm-go').className = spec.danger ? 'danger' : 'primary';
|
|
95
|
+
$('confirm-go').textContent = spec.go || 'Confirm';
|
|
96
|
+
if (spec.focus) $(spec.focus).focus();
|
|
97
|
+
else if (what === 'retire') $('confirm-handle').focus();
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Offered from inside the retire warning: someone reading it has already said
|
|
102
|
+
// what they want ("not this account, here, any more") and these are the two
|
|
103
|
+
// answers that are not destruction. Switching panels rather than closing means
|
|
104
|
+
// they do not have to go and find the button themselves.
|
|
105
|
+
const openConfirm = (what) => {
|
|
106
|
+
closePanels(); // or `show` toggles: same panel, different warning
|
|
107
|
+
document.querySelector(`[data-confirm="${what}"]`).click();
|
|
108
|
+
};
|
|
109
|
+
// Park lives on the status control now; from inside the retire warning it is
|
|
110
|
+
// still one click — close the question and park.
|
|
111
|
+
$('go-park').addEventListener('click', () => { closePanels(); setStatus(true); });
|
|
112
|
+
$('go-move').addEventListener('click', () => openConfirm('move'));
|
|
113
|
+
|
|
114
|
+
$('confirm-cancel').addEventListener('click', () => { closeConfirm(); say('nothing changed'); });
|
|
115
|
+
|
|
116
|
+
$('confirm-form').addEventListener('submit', async (ev) => {
|
|
117
|
+
ev.preventDefault();
|
|
118
|
+
if (!pending) return;
|
|
119
|
+
const what = pending;
|
|
120
|
+
const body = what === 'retire' ? { confirm: $('confirm-handle').value.trim() }
|
|
121
|
+
: what === 'move' ? { target: $('move-target').value.trim(), confirm: $('confirm-handle-move').value.trim() }
|
|
122
|
+
// Only where the field is shown (the browser build — see index.html).
|
|
123
|
+
// On the Node agent the row stays hidden and nothing is sent, which is
|
|
124
|
+
// what that agent expects.
|
|
125
|
+
: what === 'rotate-key' && !$('rotate-pw-row').hidden
|
|
126
|
+
? { password: $('rotate-password').value }
|
|
127
|
+
: {};
|
|
128
|
+
if (what === 'move' && !body.target) { say('name the account to transfer to', 'err'); return; }
|
|
129
|
+
$('confirm-go').disabled = true;
|
|
130
|
+
say(`${what} — this talks to the pod and to other servers, so it takes a moment`);
|
|
131
|
+
const r = await write(LIFECYCLE[what].path, body, what);
|
|
132
|
+
$('confirm-go').disabled = false;
|
|
133
|
+
if (!r) return; // write() already said why
|
|
134
|
+
closeConfirm();
|
|
135
|
+
say(LIFECYCLE[what].done(r));
|
|
136
|
+
load(); // mode, and whether it is retired, both changed
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// ---- inbox ----
|
|
140
|
+
// Only appears when there is enough waiting to be worth a decision. The agent
|
|
141
|
+
// drains oldest-first regardless; this exists to let the owner say "do not
|
|
142
|
+
// bother with that fortnight", which is not a call an agent should make on
|
|
143
|
+
// someone's mail by itself.
|
|
144
|
+
const INBOX_PROMPT_AT = 500;
|
|
145
|
+
let dismissed = false;
|
|
146
|
+
|
|
147
|
+
async function renderInbox() {
|
|
148
|
+
if (dismissed) return;
|
|
149
|
+
const { json: st } = await api('/status');
|
|
150
|
+
const box = st?.inbox;
|
|
151
|
+
const panel = $('pane-inbox');
|
|
152
|
+
if (!box || box.count < INBOX_PROMPT_AT) { panel.hidden = true; return; }
|
|
153
|
+
const mb = box.bytes >= 1048576
|
|
154
|
+
? `${(box.bytes / 1048576).toFixed(1)} MB` : `${Math.round(box.bytes / 1024)} kB`;
|
|
155
|
+
const since = box.oldest ? new Date(box.oldest).toLocaleDateString() : 'unknown';
|
|
156
|
+
$('inbox-summary').textContent =
|
|
157
|
+
`${box.count.toLocaleString()} deliveries waiting (${mb}), the oldest from ${since}.`;
|
|
158
|
+
// One request each to read and delete, and the agent holds itself to 60 a
|
|
159
|
+
// minute, so the honest number is minutes not seconds.
|
|
160
|
+
$('inbox-warn').hidden = box.count < 2000;
|
|
161
|
+
$('inbox-warn').textContent = box.count >= 2000
|
|
162
|
+
? `At 60 requests a minute this is roughly ${Math.ceil(box.count * 2 / 60)} minutes of `
|
|
163
|
+
+ 'draining if you keep everything. Discarding the old content is much quicker.'
|
|
164
|
+
: '';
|
|
165
|
+
panel.hidden = false;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// The gateway, as a facts row under software: attach through a multi-user
|
|
169
|
+
// front with this agent's own credential, detach back to the pod inbox. The
|
|
170
|
+
// forms live in the floating window, like every other disclosure.
|
package/web/app/README.md
CHANGED
|
@@ -5,13 +5,13 @@ See `claude/plans/browser-agent.md` for the whole design and status.
|
|
|
5
5
|
|
|
6
6
|
| file | what |
|
|
7
7
|
|---|---|
|
|
8
|
-
| `pod-auth.mjs` | The pod side of sign-in, browser-native: create a CSS account + pod, mint a client credential, and a DPoP-bound `fetch` that writes to the pod. The twin of `lib/account.mjs` + `vendor/idp-grant.cjs`. |
|
|
8
|
+
| `pod-auth.mjs` | The pod side of sign-in, browser-native: create a CSS account + pod, mint a client credential, and a DPoP-bound `fetch` that writes to the pod. The twin of `lib/device/account.mjs` + `vendor/idp-grant.cjs`. |
|
|
9
9
|
| `keystore.mjs` | WebCrypto RSA/Ed25519 key generation, and wrapping the keys under the account password (PBKDF2-SHA256 + AES-GCM-256). The pod holds only the wrapped form, so the pod's host cannot sign as you. |
|
|
10
10
|
| `keys-browser.mjs` | Importing a keys record for signing, and finding one: this browser's opened copy in IndexedDB first, else the pod's. A wrapped one the browser has not opened yet raises `KeyPasswordNeeded`, which `boot.mjs` answers with the unlock pane — once per browser. |
|
|
11
11
|
| `signup.mjs` | The `fedipod setup` flow, in the browser, up to publish: account, pod, credential, keys locked on the pod (owner-only ACL written *before* the key). Produces the credential/keys/config shapes the agent already reads. |
|
|
12
12
|
| `shims/fedify-sig.mjs` | Browser stand-in for `@fedify/fedify/sig` (which will not bundle for a browser). `sign()` returns signed headers as data for the relay; `signRequest()` wraps it Fedify-shaped. Proven byte-identical to Fedify. |
|
|
13
13
|
| `shims/node-crypto.mjs` | Browser stand-in for `node:crypto` — the small synchronous slice the agent uses, via crypto-browserify, plus native WebCrypto. |
|
|
14
|
-
| `shims/safefetch.mjs` | Browser stand-in for `lib/safefetch.mjs`. Pinning and the private-address checks are unnecessary here (a browser closes DNS rebinding itself); the BYTE BUDGET is not, so `readCapped` streams and stops at the cap exactly as the Node one does. |
|
|
14
|
+
| `shims/safefetch.mjs` | Browser stand-in for `lib/shared/safefetch.mjs`. Pinning and the private-address checks are unnecessary here (a browser closes DNS rebinding itself); the BYTE BUDGET is not, so `readCapped` streams and stops at the cap exactly as the Node one does. |
|
|
15
15
|
| `dist/` | The bundled agent, built by `scripts/build-app.mjs`. Committed like `phanpy/dist`, regenerated on release. (Not present until the agent entry is built.) |
|
|
16
16
|
|
|
17
17
|
Build: `node scripts/build-app.mjs`. Tests live in `claude/validation/`:
|
|
@@ -39,12 +39,12 @@ worker answers the data endpoints it calls. See
|
|
|
39
39
|
|
|
40
40
|
| file | what |
|
|
41
41
|
|---|---|
|
|
42
|
-
| `admin-facade.mjs` | Answers the owner/manage endpoints (`/status`, `/config`, `/gateway`, `/alias`, `/rotate-key`, `/rebuild`, `/move`, `/retire`, `/inbox/prune`, `/deadletter`, `/blocks`, `/atproto*`, `/fediacct*`) over the browser agent, mirroring `lib/admin.mjs`. Personal only. |
|
|
43
|
-
| `atproto-browser.mjs` | The Bluesky connection (`lib/atproto.mjs`) with a per-connection storage choice — IndexedDB (this browser) or owner-only pod state — plus pause. |
|
|
44
|
-
| `fediacct-browser.mjs` | Connections to fediverse accounts on other servers (`lib/fediacct.mjs`): the OAuth dance, the same storage choice, and a best-effort server-side revoke on disconnect. |
|
|
42
|
+
| `admin-facade.mjs` | Answers the owner/manage endpoints (`/status`, `/config`, `/gateway`, `/alias`, `/rotate-key`, `/rebuild`, `/move`, `/retire`, `/inbox/prune`, `/deadletter`, `/blocks`, `/atproto*`, `/fediacct*`) over the browser agent, mirroring `lib/device/admin.mjs`. Personal only. |
|
|
43
|
+
| `atproto-browser.mjs` | The Bluesky connection (`lib/connections/atproto.mjs`) with a per-connection storage choice — IndexedDB (this browser) or owner-only pod state — plus pause. |
|
|
44
|
+
| `fediacct-browser.mjs` | Connections to fediverse accounts on other servers (`lib/connections/fediacct.mjs`): the OAuth dance, the same storage choice, and a best-effort server-side revoke on disconnect. |
|
|
45
45
|
| `idb-kv.mjs` | The per-origin IndexedDB key/value store the two connectors use for "on this device, never the pod" credentials. |
|
|
46
46
|
|
|
47
|
-
`agent.mjs` also holds the single-active-agent **lease** (`lib/lease.mjs`) — a
|
|
47
|
+
`agent.mjs` also holds the single-active-agent **lease** (`lib/core/lease.mjs`) — a
|
|
48
48
|
second device runs read-only until it takes over — and starts the drain/mirrors
|
|
49
49
|
only when it is the active holder.
|
|
50
50
|
|
package/web/app/admin-facade.mjs
CHANGED
|
@@ -14,10 +14,10 @@
|
|
|
14
14
|
//
|
|
15
15
|
// It mirrors lib/admin.mjs's handlers for those paths, calling the same agent
|
|
16
16
|
// objects (store, publisher, intake, deliverer, remote) the Node agent does.
|
|
17
|
-
import { publicHandle, webfingerHost } from '../../lib/wire.mjs';
|
|
17
|
+
import { publicHandle, webfingerHost } from '../../lib/core/wire.mjs';
|
|
18
18
|
import * as podInbox from '../../lib/pod/inbox.mjs';
|
|
19
|
-
import { normalizeImport, IMPORT_KINDS } from '../../lib/import.mjs';
|
|
20
|
-
import { hashPassword } from '../../lib/
|
|
19
|
+
import { normalizeImport, IMPORT_KINDS } from '../../lib/connections/import.mjs';
|
|
20
|
+
import { hashPassword } from '../../lib/client/masto/index.mjs';
|
|
21
21
|
|
|
22
22
|
// The identity itself — changing any means a different actor, i.e. a new setup.
|
|
23
23
|
const PERMANENT_CONFIG = ['handle', 'remotePod', 'issuer', 'root', 'kind'];
|
package/web/app/agent.mjs
CHANGED
|
@@ -5,16 +5,16 @@
|
|
|
5
5
|
// the signing key comes from WebCrypto (keys-browser), and delivery goes through
|
|
6
6
|
// the relay (deliver-relay). Everything between — wire, the store, the
|
|
7
7
|
// publisher, intake, the Mastodon facade — is lib/, unchanged.
|
|
8
|
-
import { apUrls } from '../../lib/wire.mjs';
|
|
8
|
+
import { apUrls } from '../../lib/core/wire.mjs';
|
|
9
9
|
import * as containers from '../../lib/pod/containers.mjs';
|
|
10
10
|
import * as podState from '../../lib/pod/state.mjs';
|
|
11
|
-
import { PodStore } from '../../lib/store.mjs';
|
|
12
|
-
import { HttpStorage } from '../../lib/storage.mjs';
|
|
13
|
-
import { Publisher } from '../../lib/publisher.mjs';
|
|
14
|
-
import { Intake } from '../../lib/intake.mjs';
|
|
15
|
-
import { Lease } from '../../lib/lease.mjs';
|
|
16
|
-
import { MastoApi } from '../../lib/
|
|
17
|
-
import { TagFeed } from '../../lib/tagfeed.mjs';
|
|
11
|
+
import { PodStore } from '../../lib/core/store.mjs';
|
|
12
|
+
import { HttpStorage } from '../../lib/core/storage.mjs';
|
|
13
|
+
import { Publisher } from '../../lib/core/publisher/index.mjs';
|
|
14
|
+
import { Intake } from '../../lib/core/intake/index.mjs';
|
|
15
|
+
import { Lease } from '../../lib/core/lease.mjs';
|
|
16
|
+
import { MastoApi } from '../../lib/client/masto/index.mjs';
|
|
17
|
+
import { TagFeed } from '../../lib/connections/tagfeed.mjs';
|
|
18
18
|
import { makeDpopSession } from './pod-auth.mjs';
|
|
19
19
|
import { BrowserRemotePod } from './pod-remote.mjs';
|
|
20
20
|
import { importSigningKey, loadKeysFromPod, keyCacheKey } from './keys-browser.mjs';
|
|
@@ -23,11 +23,11 @@ import { kvPut } from './idb-kv.mjs';
|
|
|
23
23
|
import { RelayDeliverer } from './deliver-relay.mjs';
|
|
24
24
|
import { AdminFacade } from './admin-facade.mjs';
|
|
25
25
|
import { BrowserAtproto } from './atproto-browser.mjs';
|
|
26
|
-
import { BskyFeed } from '../../lib/bskyfeed.mjs';
|
|
26
|
+
import { BskyFeed } from '../../lib/connections/bskyfeed.mjs';
|
|
27
27
|
import { BrowserFediAccounts } from './fediacct-browser.mjs';
|
|
28
|
-
import { AcctFeed } from '../../lib/acctfeed.mjs';
|
|
29
|
-
import { followActor, unfollowActor } from '../../lib/social.mjs';
|
|
30
|
-
import { ImportWorker } from '../../lib/import.mjs';
|
|
28
|
+
import { AcctFeed } from '../../lib/connections/acctfeed.mjs';
|
|
29
|
+
import { followActor, unfollowActor } from '../../lib/core/social.mjs';
|
|
30
|
+
import { ImportWorker } from '../../lib/connections/import.mjs';
|
|
31
31
|
|
|
32
32
|
// The authorities this identity answers on: exactly one, this origin. The Node
|
|
33
33
|
// agent gets this from lib/guard.mjs, which is not in the browser bundle and
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// 2. The mirror can be paused (feedPaused) without disconnecting.
|
|
11
11
|
//
|
|
12
12
|
// Bluesky's API answers CORS, so the fetches go direct, not through the relay.
|
|
13
|
-
import { Atproto } from '../../lib/atproto.mjs';
|
|
13
|
+
import { Atproto } from '../../lib/connections/atproto.mjs';
|
|
14
14
|
import { kvGet, kvPut, kvDel } from './idb-kv.mjs';
|
|
15
15
|
|
|
16
16
|
const IDB_KEY = 'atproto';
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// which sends it verbatim. Everything else — the retry queue, the cooling
|
|
8
8
|
// hosts, the dead-letter, FEP-8b32 proofs — is the Node Deliverer, reused by
|
|
9
9
|
// overriding only the one network step.
|
|
10
|
-
import { Deliverer } from '../../lib/deliver.mjs';
|
|
10
|
+
import { Deliverer } from '../../lib/core/deliver.mjs';
|
|
11
11
|
import { sign } from './shims/fedify-sig.mjs';
|
|
12
12
|
|
|
13
13
|
export class RelayDeliverer extends Deliverer {
|