fedipod-server 0.28.1 → 0.29.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 +27 -28
- package/config/server.json +22 -0
- package/dist/claims.js +3 -2
- package/dist/directory.d.ts +2 -0
- package/dist/handler.d.ts +4 -1
- package/dist/handler.js +32 -3
- package/dist/handler.jsonld +4 -0
- package/dist/store-pod.js +37 -0
- package/lib/client/c2s.mjs +97 -43
- package/lib/client/masto/accounts.mjs +1 -0
- package/lib/core/deliver.mjs +17 -1
- package/lib/core/intake/activities.mjs +18 -2
- package/lib/core/intake/group.mjs +3 -1
- package/lib/core/intake/index.mjs +33 -2
- package/lib/core/intake/notes.mjs +12 -5
- package/lib/core/place.mjs +82 -0
- package/lib/core/publisher/collections.mjs +37 -2
- package/lib/core/publisher/index.mjs +23 -1
- package/lib/core/publisher/notes.mjs +73 -7
- package/lib/core/publisher/own.mjs +143 -0
- package/lib/core/publisher/questions.mjs +5 -3
- package/lib/core/social.mjs +62 -32
- package/lib/core/store.mjs +11 -1
- package/lib/core/wire.mjs +51 -15
- package/lib/device/admin/routes/lifecycle.mjs +1 -1
- package/lib/device/admin/routes/setup.mjs +17 -1
- package/lib/device/cli/commands/setup.mjs +50 -8
- package/lib/device/cli/context.mjs +1 -1
- package/lib/device/migrate.mjs +1 -1
- package/lib/device/setup.mjs +45 -8
- package/lib/gateway/front-core.mjs +54 -58
- package/lib/gateway/gateway-core.mjs +87 -8
- package/lib/gateway/relay-extras.mjs +89 -0
- package/lib/gateway/token-claims.mjs +16 -0
- package/lib/pod/location.mjs +52 -0
- package/lib/pod/notes.mjs +2 -4
- package/lib/pod/transport.mjs +171 -22
- package/lib/pod/type-index.mjs +101 -0
- package/lib/pod/urls.mjs +6 -0
- package/lib/server/embed.mjs +7 -6
- package/lib/session/fedi-account.mjs +19 -10
- package/lib/session/package.json +1 -1
- package/package.json +1 -1
- package/run-agent.mjs +2 -0
- package/web/admin/actors.js +2 -0
- package/web/admin/index.html +9 -0
- package/web/admin/record.js +4 -1
- package/web/admin/setup/index.html +17 -1
- package/web/admin/setup/setup.js +18 -5
- package/web/app/README.md +1 -1
- package/web/app/admin-facade.mjs +1 -1
- package/web/app/agent.mjs +11 -2
- package/web/app/boot.mjs +72 -32
- package/web/app/dist/boot.js +430 -86
- package/web/app/dist/boot.js.map +4 -4
- package/web/app/dist/sw.js +3294 -2648
- package/web/app/dist/sw.js.map +4 -4
- package/web/app/index.html +16 -0
- package/web/app/signup.mjs +65 -26
- package/web/front/run.html +7 -1
- package/web/front/run.js +30 -4
package/web/app/agent.mjs
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
// the relay (deliver-relay). Everything between — wire, the store, the
|
|
7
7
|
// publisher, intake, the Mastodon facade — is lib/, unchanged.
|
|
8
8
|
import { kvGet, kvPut } from './idb-kv.mjs';
|
|
9
|
-
import { apUrls } from '../../lib/core/wire.mjs';
|
|
9
|
+
import { apUrls, DEFAULT_ROOT } from '../../lib/core/wire.mjs';
|
|
10
|
+
import { findAccount } from '../../lib/core/place.mjs';
|
|
10
11
|
import * as containers from '../../lib/pod/containers.mjs';
|
|
11
12
|
import * as podState from '../../lib/pod/state.mjs';
|
|
12
13
|
import { PodStore } from '../../lib/core/store.mjs';
|
|
@@ -180,6 +181,8 @@ export class BrowserAgent {
|
|
|
180
181
|
// Own posts the outbox names and the timeline index lacks come back
|
|
181
182
|
// here, before anything acts on the index.
|
|
182
183
|
await this.publisher.healStatuses().catch((e) => this.log(`healing the timeline index: ${e.message}`));
|
|
184
|
+
// Likes made before the liked list existed become its first entries.
|
|
185
|
+
try { this.publisher.backfillLiked(); } catch (e) { this.log(`liked list: ${e.message}`); }
|
|
183
186
|
await this.publisher.publishProfilePage().catch((e) => this.log(`profile page: ${e.message}`));
|
|
184
187
|
// And start delivering again, since demote() stopped it. startQueue() is
|
|
185
188
|
// idempotent, so a goActive() that was already active costs nothing.
|
|
@@ -289,8 +292,14 @@ export class BrowserAgent {
|
|
|
289
292
|
// The pod session's own fetch, for the gateway APIs that take it as
|
|
290
293
|
// proof of the pod (attach, move) — the transport below is for the pod.
|
|
291
294
|
this.sessionFetch = session.fetch;
|
|
292
|
-
const root = (config && config.root) || 'fedipod/';
|
|
293
295
|
this.remote = new BrowserRemotePod(session, { webId, log: this.log });
|
|
296
|
+
// Where the account lives: handed in on sign-up, else what its owner's type
|
|
297
|
+
// index records, else `fedipod/` for an account older than the choice.
|
|
298
|
+
const root = (config && config.root)
|
|
299
|
+
|| (await findAccount(this.remote, remotePod,
|
|
300
|
+
(r) => podState.readConfig(this.remote, { state: `${remotePod}${r}ap-state/` }),
|
|
301
|
+
(c) => (c.kind || 'person') === 'person').catch(() => null))?.root
|
|
302
|
+
|| DEFAULT_ROOT;
|
|
294
303
|
// Pod-native for now: the state store below is read with these, and only
|
|
295
304
|
// the config it holds says whether this identity is fronted.
|
|
296
305
|
this.urls = apUrls(remotePod, root);
|
package/web/app/boot.mjs
CHANGED
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
// fedipodMoveIn(answers) brings the address here.
|
|
15
15
|
// Returning: fedipodSignin({ address }) — redirect to the pod's login.
|
|
16
16
|
// On every load: fedipodOnLoad() — finish a redirect, or restore, then boot.
|
|
17
|
-
import { signUp, moveIn, readAccount, handleProblem
|
|
17
|
+
import { signUp, moveIn, readAccount, handleProblem } from './signup.mjs';
|
|
18
|
+
import { podRootPath, chosenRoot } from '../../lib/core/place.mjs';
|
|
18
19
|
import * as podActor from '../../lib/pod/actor.mjs';
|
|
19
20
|
import * as podState from '../../lib/pod/state.mjs';
|
|
20
21
|
import { podBaseOfWebId } from '../../lib/pod/urls.mjs';
|
|
@@ -76,16 +77,15 @@ async function bootWorker({ reset = false } = {}) {
|
|
|
76
77
|
async function readAccountState() {
|
|
77
78
|
const session = await getSession();
|
|
78
79
|
if (!session) throw new Error('Sign in first.');
|
|
79
|
-
|
|
80
|
-
|
|
80
|
+
// Wherever its owner put it: the type index says, or it is an older account
|
|
81
|
+
// at `fedipod/` (signup.mjs readAccount).
|
|
82
|
+
const here = await readAccount(session);
|
|
83
|
+
if (!here) throw new Error(`could not find a FediPod account on ${podBaseOfWebId(session.webId)}`);
|
|
81
84
|
const remote = new BrowserRemotePod(session, { webId: session.webId, role: 'signup', log: () => {} });
|
|
82
|
-
const urls = { state };
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if (!cfg) throw new Error(`could not read this account's config under ${state}`);
|
|
87
|
-
const actorUrl = `${cfg.remotePod}${cfg.root || AP_ROOT}ap/actor`;
|
|
88
|
-
return { remote, urls, cfg, doc, actorUrl };
|
|
85
|
+
const urls = { state: `${here.pod}${here.root}ap-state/` };
|
|
86
|
+
const doc = await podState.readKeys(remote, urls);
|
|
87
|
+
const actorUrl = `${here.config.remotePod || here.pod}${here.config.root || here.root}ap/actor`;
|
|
88
|
+
return { remote, urls, cfg: here.config, doc, actorUrl };
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
window.fedipodUnlock = async (password) => {
|
|
@@ -161,22 +161,33 @@ export function parseAddress(input) {
|
|
|
161
161
|
if (!handle || !host || !host.includes('.')) return null;
|
|
162
162
|
return { handle, host };
|
|
163
163
|
}
|
|
164
|
-
async function
|
|
164
|
+
async function issuerForActor(actorUrl) {
|
|
165
165
|
// The pod's actor says where a client signs in (oauthAuthorizationEndpoint's
|
|
166
|
-
// origin)
|
|
167
|
-
//
|
|
166
|
+
// origin). Failing that: a host that answers OpenID discovery itself is the
|
|
167
|
+
// provider (a suffixed pod), and otherwise the provider is
|
|
168
|
+
// the pod host's parent domain (a subdomain pod).
|
|
168
169
|
try {
|
|
169
|
-
const authz = await podActor.readIssuer(
|
|
170
|
+
const authz = await podActor.readIssuer(actorUrl);
|
|
170
171
|
if (authz) return new URL(authz).origin;
|
|
171
172
|
} catch { /* fall through */ }
|
|
172
|
-
const u = new URL(
|
|
173
|
-
|
|
173
|
+
const u = new URL(actorUrl);
|
|
174
|
+
const own = await fetch(`${u.origin}/.well-known/openid-configuration`, { headers: { accept: 'application/json' } }).catch(() => null);
|
|
175
|
+
if (own?.ok) return u.origin;
|
|
174
176
|
const parent = u.host.split('.').slice(1).join('.');
|
|
175
177
|
return `https://${parent || u.host}`;
|
|
176
178
|
}
|
|
179
|
+
// An address on a pod names its actor through the pod host's own WebFinger;
|
|
180
|
+
// an older pod that does not answer is taken to keep it at `fedipod/`.
|
|
181
|
+
async function actorForPodAddress(handle, host) {
|
|
182
|
+
const res = await fetch(`https://${host}/.well-known/webfinger?resource=${encodeURIComponent(`acct:${handle}@${host}`)}`,
|
|
183
|
+
{ headers: { accept: 'application/jrd+json, application/json' } }).catch(() => null);
|
|
184
|
+
const doc = res?.ok ? await res.json().catch(() => ({})) : {};
|
|
185
|
+
const self = (doc.links || []).find((l) => l.rel === 'self' && /activity\+json|ld\+json/u.test(l.type || ''));
|
|
186
|
+
return self?.href || `https://${host}/fedipod/ap/actor`;
|
|
187
|
+
}
|
|
177
188
|
// An address at this site names a fronted identity. Its documents live on a
|
|
178
189
|
// pod this site's WebFinger names as an alias — the pod's own actor id.
|
|
179
|
-
async function
|
|
190
|
+
async function actorForFrontedAddress(handle) {
|
|
180
191
|
const res = await fetch(`/.well-known/webfinger?resource=${encodeURIComponent(`acct:${handle}@${location.host}`)}`,
|
|
181
192
|
{ headers: { accept: 'application/jrd+json, application/json' } }).catch(() => null);
|
|
182
193
|
if (res?.status === 410) throw new Error(`@${handle}@${location.host} is closed: nothing on the pod behind it was touched, but the address is gone for good.`);
|
|
@@ -184,19 +195,17 @@ async function podForFrontedAddress(handle) {
|
|
|
184
195
|
const doc = await res.json().catch(() => ({}));
|
|
185
196
|
const podActorId = (doc.aliases || []).find((a) => /\/ap\/actor$/u.test(String(a)));
|
|
186
197
|
if (!podActorId) throw new Error(`@${handle}@${location.host} lives here but names no pod to sign in to`);
|
|
187
|
-
|
|
188
|
-
if (!podActorId.endsWith(tail)) throw new Error(`the pod actor ${podActorId} is not where a FediPod pod keeps one`);
|
|
189
|
-
return podActorId.slice(0, -tail.length);
|
|
198
|
+
return podActorId;
|
|
190
199
|
}
|
|
191
200
|
window.fedipodSignin = async ({ address }) => {
|
|
192
201
|
const parsed = parseAddress(address);
|
|
193
202
|
if (!parsed) throw new Error('Enter your address as @you@yourpod (for example @alice@alice.solidcommunity.net).');
|
|
194
203
|
const bad = handleProblem(parsed.handle);
|
|
195
204
|
if (bad) throw new Error(bad);
|
|
196
|
-
const
|
|
197
|
-
? await
|
|
198
|
-
:
|
|
199
|
-
const issuer = await
|
|
205
|
+
const actor = parsed.host === location.host.toLowerCase()
|
|
206
|
+
? await actorForFrontedAddress(parsed.handle)
|
|
207
|
+
: await actorForPodAddress(parsed.handle, parsed.host);
|
|
208
|
+
const issuer = await issuerForActor(actor);
|
|
200
209
|
const { authorizationUrl } = await beginLogin({ issuer, redirectUri: REDIRECT });
|
|
201
210
|
location.href = authorizationUrl;
|
|
202
211
|
};
|
|
@@ -445,6 +454,10 @@ if (typeof document !== 'undefined') (async () => {
|
|
|
445
454
|
$('loading').hidden = true; $('hero').hidden = true; $('landing').hidden = true; $('running').hidden = true;
|
|
446
455
|
$('brand').hidden = false; $('pane-form').hidden = false;
|
|
447
456
|
$('signed-pod').textContent = signedPod;
|
|
457
|
+
// The container that holds `fedipod/`: the pod's own root unless they say
|
|
458
|
+
// otherwise. A move keeps the place the account already has.
|
|
459
|
+
$('container').placeholder = podRootPath(signedPod);
|
|
460
|
+
$('container-field').hidden = !!moveFrom;
|
|
448
461
|
$('movein-note').hidden = !moveFrom;
|
|
449
462
|
if (moveFrom) {
|
|
450
463
|
$('movein-from').textContent = moveFrom.address;
|
|
@@ -484,7 +497,13 @@ if (typeof document !== 'undefined') (async () => {
|
|
|
484
497
|
const pathPod = () => { try { return new URL(signedPod).pathname !== '/'; } catch { return false; } };
|
|
485
498
|
// A move between gateways is an address at a gateway: no choice either.
|
|
486
499
|
const shape = () => ((pathPod() || moveFrom) ? 'front' : f().shape.value);
|
|
487
|
-
const answers = () => ({ handle: f().handle.value.trim().toLowerCase(), shape: shape() });
|
|
500
|
+
const answers = () => ({ handle: f().handle.value.trim().toLowerCase(), shape: shape(), container: f().container.value.trim() });
|
|
501
|
+
// Where the data will be, in full, as the person types.
|
|
502
|
+
const previewPlace = () => {
|
|
503
|
+
if (!signedPod) return;
|
|
504
|
+
const r = chosenRoot(signedPod, f().container.value);
|
|
505
|
+
$('place').textContent = r.problem ? '…' : signedPod + r.root;
|
|
506
|
+
};
|
|
488
507
|
const previewAddr = () => {
|
|
489
508
|
const handle = f().handle.value.trim().toLowerCase();
|
|
490
509
|
let host = '';
|
|
@@ -507,7 +526,7 @@ if (typeof document !== 'undefined') (async () => {
|
|
|
507
526
|
$('register-link').href = url || '#';
|
|
508
527
|
$('register-link').textContent = providerHost() ? `Create your pod at ${providerHost()}` : 'Create your pod at your provider';
|
|
509
528
|
};
|
|
510
|
-
for (const el of $('form').elements) for (const evt of ['input', 'change']) el.addEventListener(evt, () => { applyMode(); applyShape(); previewAddr(); });
|
|
529
|
+
for (const el of $('form').elements) for (const evt of ['input', 'change']) el.addEventListener(evt, () => { applyMode(); applyShape(); previewAddr(); previewPlace(); });
|
|
511
530
|
applyMode();
|
|
512
531
|
|
|
513
532
|
// Step machine: one screen at a time, each gated by its own validation.
|
|
@@ -516,7 +535,7 @@ if (typeof document !== 'undefined') (async () => {
|
|
|
516
535
|
const goStep = (n) => {
|
|
517
536
|
STEP_IDS.forEach((id, i) => { $(id).hidden = i !== n - 1; });
|
|
518
537
|
$('err-1').textContent = ''; $('form-error').textContent = '';
|
|
519
|
-
if (n === 2) { applyShape(); previewAddr(); }
|
|
538
|
+
if (n === 2) { applyShape(); previewAddr(); previewPlace(); $('index-ask').hidden = true; }
|
|
520
539
|
if (FOCUS[n]) $(FOCUS[n]).focus();
|
|
521
540
|
};
|
|
522
541
|
const validateStep1 = () => {
|
|
@@ -526,6 +545,10 @@ if (typeof document !== 'undefined') (async () => {
|
|
|
526
545
|
const validateStep2 = () => {
|
|
527
546
|
const hp = window.fedipodHandleProblem(f().handle.value.trim().toLowerCase());
|
|
528
547
|
if (hp) return `Fediverse handle: ${hp}`;
|
|
548
|
+
if (!moveFrom) {
|
|
549
|
+
const where = chosenRoot(signedPod, f().container.value).problem;
|
|
550
|
+
if (where) return `Where to store it: ${where}`;
|
|
551
|
+
}
|
|
529
552
|
return null;
|
|
530
553
|
};
|
|
531
554
|
$('create').addEventListener('click', showForm);
|
|
@@ -549,12 +572,12 @@ if (typeof document !== 'undefined') (async () => {
|
|
|
549
572
|
$('run-retry').addEventListener('click', () => backToForm($('run-error').textContent));
|
|
550
573
|
$('run-back').addEventListener('click', () => { showLanding(); goStep(1); });
|
|
551
574
|
|
|
552
|
-
const LABELS = { pod: 'Checking your pod', keys: 'Making your signing key', gateway: 'Connecting your mail door'
|
|
575
|
+
const LABELS = { pod: 'Checking your pod', keys: 'Making your signing key', gateway: 'Connecting your mail door',
|
|
576
|
+
place: 'Recording where your account lives' };
|
|
553
577
|
const MOVE_LABELS = { pod: 'Reading your account on your pod', gateway: 'Taking your address here', keys: 'Moving your key and account record' };
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
const
|
|
557
|
-
const a = answers();
|
|
578
|
+
// Run the setup. `createIndex` is the person's yes to a new public type index.
|
|
579
|
+
const run = async (createIndex = false) => {
|
|
580
|
+
const a = { ...answers(), ...(createIndex ? { createIndex: true } : {}) };
|
|
558
581
|
$('pane-form').hidden = true; $('running').hidden = false;
|
|
559
582
|
$('running-title').textContent = 'Setting up…'; $('run-error').textContent = ''; $('run-actions').hidden = true;
|
|
560
583
|
const steps = $('steps'); steps.textContent = ''; const mark = {};
|
|
@@ -566,10 +589,27 @@ if (typeof document !== 'undefined') (async () => {
|
|
|
566
589
|
location.href = '/admin/client/';
|
|
567
590
|
}
|
|
568
591
|
catch (err) {
|
|
592
|
+
// No type index: nothing has been written. Ask, on the form.
|
|
593
|
+
if (err?.code === 'needs-index') {
|
|
594
|
+
backToForm();
|
|
595
|
+
$('index-ask').hidden = false;
|
|
596
|
+
$('index-yes').focus();
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
569
599
|
$('running-title').textContent = 'Setup did not finish';
|
|
570
600
|
$('run-error').textContent = err.message || String(err);
|
|
571
601
|
$('run-actions').hidden = false; // the way out of the failure screen
|
|
572
602
|
}
|
|
603
|
+
};
|
|
604
|
+
$('form').addEventListener('submit', async (e) => {
|
|
605
|
+
e.preventDefault(); $('form-error').textContent = '';
|
|
606
|
+
const e2 = validateStep2(); if (e2) { $('form-error').textContent = e2; goStep(2); return; }
|
|
607
|
+
await run();
|
|
608
|
+
});
|
|
609
|
+
$('index-yes').addEventListener('click', () => { $('index-ask').hidden = true; run(true); });
|
|
610
|
+
$('index-no').addEventListener('click', () => {
|
|
611
|
+
$('index-ask').hidden = true;
|
|
612
|
+
$('form-error').textContent = 'Sign-up stopped. Nothing was written to your pod.';
|
|
573
613
|
});
|
|
574
614
|
|
|
575
615
|
if (pendingIdentity) await showIdentity();
|