fedipod-server 0.4.0 → 0.6.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 +3 -1
- package/fedipod-server.svg +81 -133
- package/lib/admin.mjs +73 -4
- package/lib/deliver.mjs +71 -8
- package/lib/gateway-core.mjs +4 -2
- package/lib/httpsig.mjs +27 -12
- package/lib/intake.mjs +19 -6
- package/lib/keys.mjs +18 -4
- package/lib/lease.mjs +10 -4
- package/lib/mastoapi.mjs +2 -2
- package/lib/proof.mjs +79 -0
- package/lib/publisher.mjs +6 -3
- package/lib/tagfeed.mjs +2 -2
- package/lib/update.mjs +7 -2
- package/lib/wire.mjs +41 -2
- package/package.json +7 -1
- package/run-agent.mjs +10 -3
- package/web/admin/admin.js +64 -5
- package/web/admin/index.html +30 -3
- package/web/front/new-account.html +11 -392
- package/web/front/new-account.html~ +50 -0
|
@@ -3,422 +3,41 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
-
<title>FediPod —
|
|
6
|
+
<title>FediPod — access the Fediverse and ATProto from a Solid pod</title>
|
|
7
7
|
<style>
|
|
8
8
|
:root { color-scheme: light dark; --line:#d9d4c7; --dim:#45443d; --accent:#6a8a5a; }
|
|
9
9
|
@media (prefers-color-scheme: dark) { :root { --dim:#c2c0b4; } }
|
|
10
10
|
body { font: 19px/1.6 system-ui, sans-serif; max-width: 44rem; margin: 2rem auto; padding: 0 1.1rem; }
|
|
11
11
|
h1 { font-size: 1.7rem; margin: 0 0 .2rem; }
|
|
12
|
-
h2 { font-size: 1.2rem; margin:
|
|
12
|
+
h2 { font-size: 1.2rem; margin: 1.4rem 0 .4rem; }
|
|
13
13
|
p.lede { color: var(--dim); margin-top: 0; }
|
|
14
|
-
fieldset { border: 1px solid var(--line); border-radius: 12px; padding: 1rem 1.1rem; margin: 1rem 0; }
|
|
15
|
-
legend { font-weight: 600; padding: 0 .4rem; }
|
|
16
|
-
.choices { display: flex; flex-direction: column; gap: .5rem; }
|
|
17
|
-
.choices button { padding: .7rem .8rem; border: 1px solid var(--line); border-radius: 10px;
|
|
18
|
-
background: transparent; font: inherit; cursor: pointer; text-align: left; }
|
|
19
|
-
.choices button[aria-pressed="true"] { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }
|
|
20
|
-
.choices button b { display: block; }
|
|
21
|
-
.choices button span { color: var(--dim); font-size: .95rem; }
|
|
22
|
-
label { display: block; font-weight: 600; margin: .8rem 0 .2rem; }
|
|
23
|
-
input[type=text], input[type=url] { width: 100%; padding: .5rem .6rem; font: inherit;
|
|
24
|
-
border: 1px solid var(--line); border-radius: 8px; box-sizing: border-box; }
|
|
25
|
-
.msg { min-height: 1.3em; font-size: .95rem; margin-top: .3rem; }
|
|
26
|
-
.msg.ok { color: var(--accent); } .msg.no { color: #a4433a; }
|
|
27
14
|
.hint { color: var(--dim); font-size: .95rem; }
|
|
28
|
-
.primary { background: var(--accent); color: #fff; border: none; border-radius: 9px;
|
|
29
|
-
padding: .6rem 1.1rem; font: inherit; cursor: pointer; }
|
|
30
|
-
.primary[disabled] { opacity: .5; cursor: not-allowed; }
|
|
31
|
-
[hidden] { display: none !important; }
|
|
32
15
|
code { background: rgba(120,120,120,.12); padding: .05em .35em; border-radius: 4px; }
|
|
33
16
|
pre { background: rgba(120,120,120,.12); padding: .7rem .9rem; border-radius: 8px; overflow-x: auto; }
|
|
34
17
|
ol.steps li { margin: .5rem 0; }
|
|
18
|
+
[hidden] { display: none !important; }
|
|
35
19
|
</style>
|
|
36
20
|
</head>
|
|
37
21
|
<body>
|
|
38
22
|
<h1>FediPod - join the fediverse from a Solid pod.</h1>
|
|
39
|
-
<p class="lede">Welcome to
|
|
23
|
+
<p class="lede">Welcome to <b>FediPod</b>! Here, you can get a free pod and fediverse account or link to your existing pod/account.</p>
|
|
24
|
+
|
|
25
|
+
<p class="hint" id="current-version" hidden>Current FediPod: <strong id="current-version-num"></strong><br>
|
|
26
|
+
— install or update an existing install with <code id="current-version-cmd"></code></p>
|
|
40
27
|
|
|
41
28
|
<p>
|
|
42
29
|
FediPod is experimental and free to use. See the <a href="https://github.com/jeff-zucker/FediPod">FediPod repository</a> for details.
|
|
43
30
|
</p>
|
|
44
31
|
|
|
45
|
-
<p class="hint" id="current-version" hidden>Current FediPod: <strong id="current-version-num"></strong>
|
|
46
|
-
— update an existing install with <code id="current-version-cmd"></code></p>
|
|
47
|
-
|
|
48
|
-
<fieldset>
|
|
49
|
-
<legend>What do you want to do?</legend>
|
|
50
|
-
<div class="choices" role="group" aria-label="Path">
|
|
51
|
-
<button type="button" id="pick-solo" aria-pressed="false"><b>Create a new solo account</b><span>a fediverse account for one person, on a pod you own</span></button>
|
|
52
|
-
<button type="button" id="pick-group" aria-pressed="false"><b>Create a new group account</b><span>an account people join — it carries members' posts to the whole group</span></button>
|
|
53
|
-
<button type="button" id="pick-manage" aria-pressed="false"><b>Manage an existing account</b><span>attach it to this gateway, detach it, or transfer in</span></button>
|
|
54
|
-
</div>
|
|
55
|
-
</fieldset>
|
|
56
|
-
|
|
57
|
-
<!-- create: the signup itself — prove the pod, take the name, then install -->
|
|
58
|
-
<form id="create-form" hidden>
|
|
59
|
-
<h2 id="create-title">Create a new solo account</h2>
|
|
60
|
-
<p class="hint" id="create-group-hint" hidden>A group is an account people join: members post to it, and it
|
|
61
|
-
carries each member's post to everyone in the group. Mastodon users can join with no pod of their own.
|
|
62
|
-
A group needs a pod of its own.</p>
|
|
63
|
-
<p class="hint">Your account lives on a pod you own; this gateway filters its mail.</p>
|
|
64
|
-
|
|
65
|
-
<label for="create-pod">Your pod address</label>
|
|
66
|
-
<input type="url" id="create-pod" autocomplete="off" placeholder="https://you.solidcommunity.net/">
|
|
67
|
-
<p class="hint">No pod yet? Any Solid pod host that offers <em>https subdomains</em> works, or a Community
|
|
68
|
-
Solid Server you run yourself. Get the pod, then come back here.</p>
|
|
69
|
-
<p id="create-pods" class="hint" hidden>This host also offers pods — you can take one instead of bringing
|
|
70
|
-
your own; it stays exportable, so you can leave with everything.</p>
|
|
71
|
-
|
|
72
|
-
<label for="create-issuer">Where you log in (your identity provider)</label>
|
|
73
|
-
<input type="url" id="create-issuer" autocomplete="off" placeholder="https://solidcommunity.net">
|
|
74
|
-
|
|
75
|
-
<label for="create-handle">Your handle (the name in your address; permanent)</label>
|
|
76
|
-
<input type="text" id="create-handle" autocomplete="off" autocapitalize="off" spellcheck="false"
|
|
77
|
-
inputmode="latin" placeholder="you" aria-describedby="create-handle-msg">
|
|
78
|
-
<p class="msg" id="create-handle-msg" aria-live="polite"></p>
|
|
79
|
-
|
|
80
|
-
<label>Your fediverse address</label>
|
|
81
|
-
<div class="choices" role="group" aria-label="Address">
|
|
82
|
-
<button type="button" id="addr-pod" aria-pressed="false"><b id="addr-pod-name">@you@your.pod</b><span>your handle is tied to your pod — you can drop the gateway and keep everything</span></button>
|
|
83
|
-
<button type="button" id="addr-front" aria-pressed="false"><b id="addr-front-name">@you@this.host</b><span>your handle is tied to the gateway — you can move pods and keep your handle</span></button>
|
|
84
|
-
</div>
|
|
85
|
-
|
|
86
|
-
<p style="margin-top:1.1rem">
|
|
87
|
-
<button type="button" id="create-continue" class="primary" disabled>Sign in with your pod & create</button>
|
|
88
|
-
</p>
|
|
89
|
-
<p class="hint">Signing in proves the pod is yours — no password ever reaches this gateway.</p>
|
|
90
|
-
<p class="hint" id="create-note" hidden></p>
|
|
91
|
-
</form>
|
|
92
|
-
|
|
93
|
-
<!-- what a finished signup hands over: the one install command -->
|
|
94
|
-
<section id="create-done" hidden></section>
|
|
95
|
-
|
|
96
|
-
<!-- manage: the existing-account actions, one panel each -->
|
|
97
|
-
<section id="manage-form" hidden>
|
|
98
|
-
<div class="choices" role="group" aria-label="Manage">
|
|
99
|
-
<button type="button" id="pick-attach" aria-pressed="false"><b>Attach to this gateway</b><span>filter your mail through this door — your address stays yours</span></button>
|
|
100
|
-
<button type="button" id="pick-detach" aria-pressed="false"><b>Detach from this gateway</b><span>go back to unfiltered delivery</span></button>
|
|
101
|
-
<button type="button" id="pick-transfer" aria-pressed="false"><b>Transfer a fediverse account to FediPod</b><span>move here from Mastodon and friends</span></button>
|
|
102
|
-
</div>
|
|
103
|
-
</section>
|
|
104
|
-
|
|
105
|
-
<!-- attach: an existing FediPod points its inbox here; identity stays put -->
|
|
106
|
-
<form id="attach-form" hidden>
|
|
107
|
-
<h2>Attach an existing FediPod account</h2>
|
|
108
|
-
<p class="hint">Your account stays <code>@you@your.pod</code> — other servers keep finding you at your own
|
|
109
|
-
address. Attaching only changes where your <em>mail</em> arrives: your actor advertises this gateway's door
|
|
110
|
-
as its inbox, and everything that passes verification lands in your pod as before.</p>
|
|
111
|
-
|
|
112
|
-
<label for="pod-url">Your pod address</label>
|
|
113
|
-
<input type="url" id="pod-url" autocomplete="off" placeholder="https://you.solidcommunity.net/">
|
|
114
|
-
<p class="hint">If your FediPod uses a custom root, give the full home URL (ending in <code>/</code>);
|
|
115
|
-
a bare pod address assumes the default <code>activitypods-js/</code>.</p>
|
|
116
|
-
|
|
117
|
-
<label for="issuer">Where you log in (your identity provider)</label>
|
|
118
|
-
<input type="url" id="issuer" autocomplete="off" placeholder="https://solidcommunity.net">
|
|
119
|
-
|
|
120
|
-
<label for="slug">Your door's name here</label>
|
|
121
|
-
<input type="text" id="slug" autocomplete="off" autocapitalize="off" spellcheck="false"
|
|
122
|
-
inputmode="latin" placeholder="you" aria-describedby="slug-msg">
|
|
123
|
-
<p class="msg" id="slug-msg" aria-live="polite"></p>
|
|
124
|
-
<p class="hint">Only the door is named here — it is a routing address for servers, not your handle.</p>
|
|
125
|
-
|
|
126
|
-
<p style="margin-top:1.1rem">
|
|
127
|
-
<button type="button" id="attach-continue" class="primary" disabled>Sign in with your pod & attach</button>
|
|
128
|
-
</p>
|
|
129
|
-
<p class="hint">Signing in proves the pod is yours — no password ever reaches this gateway.</p>
|
|
130
|
-
<p class="hint" id="attach-note" hidden></p>
|
|
131
|
-
</form>
|
|
132
|
-
|
|
133
|
-
<!-- detach: done on the user's own agent; the gateway cannot reach in -->
|
|
134
|
-
<section id="detach-form" hidden>
|
|
135
|
-
<h2>Detach an existing FediPod account</h2>
|
|
136
|
-
<p class="hint">Detaching is yours to do, not this gateway's — your agent republishes your actor with your
|
|
137
|
-
pod's own inbox, and deliveries go straight there again. On your machine:</p>
|
|
138
|
-
<pre>node bin/fedipod.mjs gateway --detach</pre>
|
|
139
|
-
<p class="hint">Your name and data never moved, so nothing else changes. The door you had here goes quiet
|
|
140
|
-
and its name can be reused.</p>
|
|
141
|
-
</section>
|
|
142
|
-
|
|
143
|
-
<!-- transfer: the standard fediverse move, aimed at a FediPod -->
|
|
144
|
-
<section id="transfer-form" hidden>
|
|
145
|
-
<h2>Transfer a fediverse account to FediPod</h2>
|
|
146
|
-
<p class="hint">The fediverse's standard migration works toward a FediPod: your followers move over
|
|
147
|
-
automatically, and your old profile redirects. Posts stay on the old server — no fediverse migration
|
|
148
|
-
carries them.</p>
|
|
149
|
-
<ol class="steps">
|
|
150
|
-
<li><strong>Have a FediPod</strong> — create one first (the first option above).</li>
|
|
151
|
-
<li>On your agent's admin page, set <strong>new followers</strong> to <em>accepted automatically</em>,
|
|
152
|
-
so the wave of arriving followers doesn't queue.</li>
|
|
153
|
-
<li>Tell your FediPod about the old account:
|
|
154
|
-
<pre>node bin/fedipod.mjs alias --add @you@old.server</pre></li>
|
|
155
|
-
<li>On the old server: Preferences → Account → <em>Move to a different account</em>, naming your
|
|
156
|
-
FediPod address.</li>
|
|
157
|
-
<li>Bring the rest over from the old server's CSV export:
|
|
158
|
-
<pre>node bin/fedipod.mjs import following_accounts.csv blocked_accounts.csv muted_accounts.csv lists.csv</pre></li>
|
|
159
|
-
</ol>
|
|
160
|
-
</section>
|
|
161
|
-
|
|
162
|
-
<script src="/solid-client-authn.bundle.js"></script>
|
|
163
32
|
<script>
|
|
164
|
-
const $ = (id) => document.getElementById(id);
|
|
165
|
-
let offersPods = false, slugOk = false;
|
|
166
|
-
|
|
167
33
|
fetch('/api/handle?handle=__probe__').then(r => r.json()).then(d => {
|
|
168
|
-
offersPods = !!d.offersPods;
|
|
169
|
-
$('create-pods').hidden = !offersPods;
|
|
170
34
|
if (d.version) {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
35
|
+
document.getElementById('current-version-num').textContent = d.version;
|
|
36
|
+
document.getElementById('current-version-cmd').textContent = `curl -fsSL ${location.origin}/install | sh`;
|
|
37
|
+
document.getElementById('current-version').hidden = false;
|
|
174
38
|
}
|
|
175
39
|
}).catch(() => {});
|
|
176
|
-
|
|
177
|
-
const picks = ['pick-solo', 'pick-group', 'pick-manage'];
|
|
178
|
-
const panels = { 'pick-solo': 'create-form', 'pick-group': 'create-form',
|
|
179
|
-
'pick-manage': 'manage-form' };
|
|
180
|
-
const subPicks = ['pick-attach', 'pick-detach', 'pick-transfer'];
|
|
181
|
-
const subPanels = { 'pick-attach': 'attach-form', 'pick-detach': 'detach-form',
|
|
182
|
-
'pick-transfer': 'transfer-form' };
|
|
183
|
-
let createKind = 'person';
|
|
184
|
-
function subPick(which) {
|
|
185
|
-
for (const id of subPicks) {
|
|
186
|
-
$(id).setAttribute('aria-pressed', String(id === which));
|
|
187
|
-
$(subPanels[id]).hidden = id !== which;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
function pick(which) {
|
|
191
|
-
for (const id of picks) $(id).setAttribute('aria-pressed', String(id === which));
|
|
192
|
-
for (const panel of new Set(Object.values(panels))) $(panel).hidden = panels[which] !== panel;
|
|
193
|
-
$('create-done').hidden = true;
|
|
194
|
-
if (which !== 'pick-manage') subPick(null);
|
|
195
|
-
if (panels[which] === 'create-form') {
|
|
196
|
-
createKind = which === 'pick-group' ? 'group' : 'person';
|
|
197
|
-
$('create-title').textContent = createKind === 'group'
|
|
198
|
-
? 'Create a new group account' : 'Create a new solo account';
|
|
199
|
-
$('create-group-hint').hidden = createKind !== 'group';
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
for (const id of picks) $(id).onclick = () => pick(id);
|
|
203
|
-
for (const id of subPicks) $(id).onclick = () => subPick(id);
|
|
204
|
-
|
|
205
|
-
// Suggest the door name from the pod's own first label, and the identity
|
|
206
|
-
// provider from the pod's apex domain — both still editable.
|
|
207
|
-
$('pod-url').addEventListener('input', () => {
|
|
208
|
-
try {
|
|
209
|
-
const u = new URL($('pod-url').value.trim());
|
|
210
|
-
const label = u.hostname.split('.')[0];
|
|
211
|
-
if (!$('slug').value && /^[a-z0-9-]{1,63}$/.test(label)) {
|
|
212
|
-
$('slug').value = label;
|
|
213
|
-
$('slug').dispatchEvent(new Event('input'));
|
|
214
|
-
}
|
|
215
|
-
if (!$('issuer').value) {
|
|
216
|
-
const parts = u.hostname.split('.');
|
|
217
|
-
$('issuer').value = 'https://' + (parts.length > 2 ? parts.slice(1).join('.') : u.hostname);
|
|
218
|
-
}
|
|
219
|
-
} catch { /* not a URL yet */ }
|
|
220
|
-
refresh();
|
|
221
|
-
});
|
|
222
|
-
$('issuer').addEventListener('input', refresh);
|
|
223
|
-
|
|
224
|
-
let t = null;
|
|
225
|
-
$('slug').addEventListener('input', () => {
|
|
226
|
-
slugOk = false; $('slug-msg').textContent = ''; $('slug-msg').className = 'msg';
|
|
227
|
-
clearTimeout(t);
|
|
228
|
-
const h = $('slug').value.trim().toLowerCase();
|
|
229
|
-
if (!h) { refresh(); return; }
|
|
230
|
-
t = setTimeout(async () => {
|
|
231
|
-
try {
|
|
232
|
-
const d = await (await fetch('/api/handle?handle=' + encodeURIComponent(h))).json();
|
|
233
|
-
slugOk = d.available;
|
|
234
|
-
$('slug-msg').textContent = d.available ? 'this door is free' : (d.reason || 'taken');
|
|
235
|
-
$('slug-msg').className = 'msg ' + (d.available ? 'ok' : 'no');
|
|
236
|
-
} catch { $('slug-msg').textContent = 'could not check'; $('slug-msg').className = 'msg no'; }
|
|
237
|
-
refresh();
|
|
238
|
-
}, 250);
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
function refresh() {
|
|
242
|
-
const podOk = /^https:\/\/\S+/.test($('pod-url').value.trim());
|
|
243
|
-
const issuerOk = /^https:\/\/\S+/.test($('issuer').value.trim());
|
|
244
|
-
$('attach-continue').disabled = !(podOk && slugOk && issuerOk);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// The user's pod home: their agent tree on the pod. A bare pod root gets
|
|
248
|
-
// the default root; anything with a path is taken as given.
|
|
249
|
-
function podHomeOf(raw) {
|
|
250
|
-
const u = new URL(raw.trim());
|
|
251
|
-
if (u.pathname === '/') u.pathname = '/activitypods-js/';
|
|
252
|
-
else if (!u.pathname.endsWith('/')) u.pathname += '/';
|
|
253
|
-
u.search = ''; u.hash = '';
|
|
254
|
-
return u.href;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
$('attach-continue').onclick = () => {
|
|
258
|
-
const auth = window.solidClientAuthentication;
|
|
259
|
-
const n = $('attach-note');
|
|
260
|
-
if (!auth) { n.hidden = false; n.textContent = 'the sign-in library did not load — reload and try again'; return; }
|
|
261
|
-
sessionStorage.setItem('fp-attach', JSON.stringify({
|
|
262
|
-
slug: $('slug').value.trim().toLowerCase(),
|
|
263
|
-
podHome: podHomeOf($('pod-url').value),
|
|
264
|
-
kind: 'person',
|
|
265
|
-
}));
|
|
266
|
-
auth.login({
|
|
267
|
-
oidcIssuer: $('issuer').value.trim(),
|
|
268
|
-
redirectUrl: location.origin + '/',
|
|
269
|
-
clientName: 'FediPod gateway',
|
|
270
|
-
}).catch((e) => { n.hidden = false; n.textContent = 'sign-in failed to start: ' + e.message; });
|
|
271
|
-
};
|
|
272
|
-
|
|
273
|
-
// ---- the create (signup) form ----
|
|
274
|
-
let createSlugOk = false, addrShape = null;
|
|
275
|
-
const esc = (s) => String(s).replace(/[&<>"']/g, (c) =>
|
|
276
|
-
({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]));
|
|
277
|
-
|
|
278
|
-
// The two address previews follow the handle and pod as they are typed.
|
|
279
|
-
function addrPreview() {
|
|
280
|
-
const h = $('create-handle').value.trim().toLowerCase() || 'you';
|
|
281
|
-
let podHost = 'your.pod';
|
|
282
|
-
try { podHost = new URL($('create-pod').value.trim()).hostname; } catch { /* not a URL yet */ }
|
|
283
|
-
$('addr-pod-name').textContent = `@${h}@${podHost}`;
|
|
284
|
-
$('addr-front-name').textContent = `@${h}@${location.host}`;
|
|
285
|
-
}
|
|
286
|
-
function createRefresh() {
|
|
287
|
-
const podOk = /^https:\/\/\S+/.test($('create-pod').value.trim());
|
|
288
|
-
const issuerOk = /^https:\/\/\S+/.test($('create-issuer').value.trim());
|
|
289
|
-
$('create-continue').disabled = !(podOk && issuerOk && createSlugOk && addrShape);
|
|
290
|
-
}
|
|
291
|
-
$('addr-pod').onclick = () => { addrShape = 'pod';
|
|
292
|
-
$('addr-pod').setAttribute('aria-pressed', 'true');
|
|
293
|
-
$('addr-front').setAttribute('aria-pressed', 'false'); createRefresh(); };
|
|
294
|
-
$('addr-front').onclick = () => { addrShape = 'front';
|
|
295
|
-
$('addr-front').setAttribute('aria-pressed', 'true');
|
|
296
|
-
$('addr-pod').setAttribute('aria-pressed', 'false'); createRefresh(); };
|
|
297
|
-
|
|
298
|
-
$('create-pod').addEventListener('input', () => {
|
|
299
|
-
try {
|
|
300
|
-
const u = new URL($('create-pod').value.trim());
|
|
301
|
-
const label = u.hostname.split('.')[0];
|
|
302
|
-
if (!$('create-handle').value && /^[a-z0-9-]{1,63}$/.test(label)) {
|
|
303
|
-
$('create-handle').value = label;
|
|
304
|
-
$('create-handle').dispatchEvent(new Event('input'));
|
|
305
|
-
}
|
|
306
|
-
if (!$('create-issuer').value) {
|
|
307
|
-
const parts = u.hostname.split('.');
|
|
308
|
-
$('create-issuer').value = 'https://' + (parts.length > 2 ? parts.slice(1).join('.') : u.hostname);
|
|
309
|
-
}
|
|
310
|
-
} catch { /* not a URL yet */ }
|
|
311
|
-
addrPreview(); createRefresh();
|
|
312
|
-
});
|
|
313
|
-
$('create-issuer').addEventListener('input', createRefresh);
|
|
314
|
-
|
|
315
|
-
let ct = null;
|
|
316
|
-
$('create-handle').addEventListener('input', () => {
|
|
317
|
-
createSlugOk = false; $('create-handle-msg').textContent = ''; $('create-handle-msg').className = 'msg';
|
|
318
|
-
addrPreview();
|
|
319
|
-
clearTimeout(ct);
|
|
320
|
-
const h = $('create-handle').value.trim().toLowerCase();
|
|
321
|
-
if (!h) { createRefresh(); return; }
|
|
322
|
-
ct = setTimeout(async () => {
|
|
323
|
-
try {
|
|
324
|
-
const d = await (await fetch('/api/handle?handle=' + encodeURIComponent(h))).json();
|
|
325
|
-
createSlugOk = d.available;
|
|
326
|
-
$('create-handle-msg').textContent = d.available ? 'this name is free' : (d.reason || 'taken');
|
|
327
|
-
$('create-handle-msg').className = 'msg ' + (d.available ? 'ok' : 'no');
|
|
328
|
-
} catch { $('create-handle-msg').textContent = 'could not check'; $('create-handle-msg').className = 'msg no'; }
|
|
329
|
-
createRefresh();
|
|
330
|
-
}, 250);
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
$('create-continue').onclick = () => {
|
|
334
|
-
const auth = window.solidClientAuthentication;
|
|
335
|
-
const n = $('create-note');
|
|
336
|
-
if (!auth) { n.hidden = false; n.textContent = 'the sign-in library did not load — reload and try again'; return; }
|
|
337
|
-
sessionStorage.setItem('fp-create', JSON.stringify({
|
|
338
|
-
slug: $('create-handle').value.trim().toLowerCase(),
|
|
339
|
-
podHome: podHomeOf($('create-pod').value),
|
|
340
|
-
issuer: $('create-issuer').value.trim(),
|
|
341
|
-
kind: createKind,
|
|
342
|
-
fronted: addrShape === 'front',
|
|
343
|
-
}));
|
|
344
|
-
auth.login({
|
|
345
|
-
oidcIssuer: $('create-issuer').value.trim(),
|
|
346
|
-
redirectUrl: location.origin + '/',
|
|
347
|
-
clientName: 'FediPod gateway',
|
|
348
|
-
}).catch((e) => { n.hidden = false; n.textContent = 'sign-in failed to start: ' + e.message; });
|
|
349
|
-
};
|
|
350
|
-
|
|
351
|
-
// Back from the identity provider: finish the signup with the proven login.
|
|
352
|
-
(async () => {
|
|
353
|
-
const auth = window.solidClientAuthentication;
|
|
354
|
-
if (!auth) return;
|
|
355
|
-
const info = await auth.handleIncomingRedirect().catch(() => null);
|
|
356
|
-
const pending = sessionStorage.getItem('fp-create');
|
|
357
|
-
if (!info?.isLoggedIn || !pending) return;
|
|
358
|
-
sessionStorage.removeItem('fp-create');
|
|
359
|
-
const p = JSON.parse(pending);
|
|
360
|
-
pick(p.kind === 'group' ? 'pick-group' : 'pick-solo');
|
|
361
|
-
const n = $('create-note');
|
|
362
|
-
n.hidden = false;
|
|
363
|
-
n.textContent = 'signed in as ' + info.webId + ' — creating…';
|
|
364
|
-
const res = await auth.fetch('/api/attach', {
|
|
365
|
-
method: 'POST', headers: { 'content-type': 'application/json' },
|
|
366
|
-
body: JSON.stringify({ handle: p.slug, podHome: p.podHome, kind: p.kind, fronted: p.fronted }),
|
|
367
|
-
}).catch(() => null);
|
|
368
|
-
const d = res ? await res.json().catch(() => ({})) : {};
|
|
369
|
-
if (res && res.status === 201) {
|
|
370
|
-
const address = p.fronted ? d.address : `@${p.slug}@${new URL(p.podHome).hostname}`;
|
|
371
|
-
const flags = [
|
|
372
|
-
`--gateway ${d.frontActor || d.doorInbox}`,
|
|
373
|
-
`--secret ${d.hmacSecret}`,
|
|
374
|
-
`--pod ${p.podHome}`,
|
|
375
|
-
`--issuer ${p.issuer}`,
|
|
376
|
-
`--handle ${p.slug}`,
|
|
377
|
-
`--kind ${p.kind}`,
|
|
378
|
-
...(p.fronted ? ['--fronted'] : []),
|
|
379
|
-
];
|
|
380
|
-
const cmd = `curl -fsSL ${location.origin}/install | sh -s -- \\\n ` + flags.join(' \\\n ');
|
|
381
|
-
$('create-form').hidden = true;
|
|
382
|
-
const done = $('create-done');
|
|
383
|
-
done.hidden = false;
|
|
384
|
-
done.innerHTML = '<h2>' + esc(address) + '</h2>'
|
|
385
|
-
+ '<p>Created. Now install your agent on the machine it will live on — one command, then start it:</p>'
|
|
386
|
-
+ '<pre>' + esc(cmd) + '\n\nfedipod start</pre>'
|
|
387
|
-
+ '<p>Setup opens in your browser with everything already filled in. When it finishes, your '
|
|
388
|
-
+ 'account is live and your mail arrives through this gateway, filtered.</p>';
|
|
389
|
-
} else {
|
|
390
|
-
n.textContent = 'signup failed: ' + (d.error || (res ? 'HTTP ' + res.status : 'no response'));
|
|
391
|
-
}
|
|
392
|
-
})();
|
|
393
|
-
|
|
394
|
-
// Back from the identity provider: finish the attach with the proven login.
|
|
395
|
-
(async () => {
|
|
396
|
-
const auth = window.solidClientAuthentication;
|
|
397
|
-
if (!auth) return;
|
|
398
|
-
const info = await auth.handleIncomingRedirect().catch(() => null);
|
|
399
|
-
const pending = sessionStorage.getItem('fp-attach');
|
|
400
|
-
if (!info?.isLoggedIn || !pending) return;
|
|
401
|
-
sessionStorage.removeItem('fp-attach');
|
|
402
|
-
const p = JSON.parse(pending);
|
|
403
|
-
pick('pick-manage');
|
|
404
|
-
subPick('pick-attach');
|
|
405
|
-
const n = $('attach-note');
|
|
406
|
-
n.hidden = false;
|
|
407
|
-
n.textContent = 'signed in as ' + info.webId + ' — attaching…';
|
|
408
|
-
const res = await auth.fetch('/api/attach', {
|
|
409
|
-
method: 'POST', headers: { 'content-type': 'application/json' },
|
|
410
|
-
body: JSON.stringify({ handle: p.slug, podHome: p.podHome, kind: p.kind }),
|
|
411
|
-
}).catch(() => null);
|
|
412
|
-
const d = res ? await res.json().catch(() => ({})) : {};
|
|
413
|
-
if (res && res.status === 201) {
|
|
414
|
-
n.innerHTML = 'Attached. Run this on the machine your agent lives on, then restart the agent:'
|
|
415
|
-
+ '<pre>' + d.command + '</pre>'
|
|
416
|
-
+ 'Your mail then arrives through <code>' + d.doorInbox + '</code>, filtered. Your name has not moved.';
|
|
417
|
-
} else {
|
|
418
|
-
n.textContent = 'attach failed: ' + (d.error || (res ? 'HTTP ' + res.status : 'no response'));
|
|
419
|
-
}
|
|
420
|
-
})();
|
|
421
40
|
</script>
|
|
422
|
-
<i>(cc) 4.0 By, Jeff Zucker, 2026</i>
|
|
41
|
+
<i style="text-align:right">(cc) 4.0 By, Jeff Zucker, 2026</i>
|
|
423
42
|
</body>
|
|
424
43
|
</html>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>FediPod — access the Fediverse and ATProto from a Solid pod</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root { color-scheme: light dark; --line:#d9d4c7; --dim:#45443d; --accent:#6a8a5a; }
|
|
9
|
+
@media (prefers-color-scheme: dark) { :root { --dim:#c2c0b4; } }
|
|
10
|
+
body { font: 19px/1.6 system-ui, sans-serif; max-width: 44rem; margin: 2rem auto; padding: 0 1.1rem; }
|
|
11
|
+
h1 { font-size: 1.7rem; margin: 0 0 .2rem; }
|
|
12
|
+
h2 { font-size: 1.2rem; margin: 1.4rem 0 .4rem; }
|
|
13
|
+
p.lede { color: var(--dim); margin-top: 0; }
|
|
14
|
+
.hint { color: var(--dim); font-size: .95rem; }
|
|
15
|
+
code { background: rgba(120,120,120,.12); padding: .05em .35em; border-radius: 4px; }
|
|
16
|
+
pre { background: rgba(120,120,120,.12); padding: .7rem .9rem; border-radius: 8px; overflow-x: auto; }
|
|
17
|
+
ol.steps li { margin: .5rem 0; }
|
|
18
|
+
[hidden] { display: none !important; }
|
|
19
|
+
</style>
|
|
20
|
+
</head>
|
|
21
|
+
<body>
|
|
22
|
+
<h1>FediPod - join the fediverse from a Solid pod.</h1>
|
|
23
|
+
<p class="lede">Welcome to <b>FediPod</b! Here, you can get a free pod and fediverse account or link to your existing pod/account.</p>
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
<p>
|
|
27
|
+
FediPod is experimental and free to use. See the <a href="https://github.com/jeff-zucker/FediPod">FediPod repository</a> for details.
|
|
28
|
+
</p>
|
|
29
|
+
|
|
30
|
+
<p class="hint" id="current-version" hidden>Current FediPod: <strong id="current-version-num"></strong><br>
|
|
31
|
+
— update an existing install with <code id="current-version-cmd"></code></p>
|
|
32
|
+
|
|
33
|
+
<script>
|
|
34
|
+
// Another host deploying this front serves the same page: the command names
|
|
35
|
+
// that host, not fedipod.net.
|
|
36
|
+
if (location.origin !== 'https://fedipod.net' && location.protocol.startsWith('http')) {
|
|
37
|
+
document.getElementById('install-cmd').textContent =
|
|
38
|
+
`curl -fsSL ${location.origin}/install | sh\n\nfedipod start`;
|
|
39
|
+
}
|
|
40
|
+
fetch('/api/handle?handle=__probe__').then(r => r.json()).then(d => {
|
|
41
|
+
if (d.version) {
|
|
42
|
+
document.getElementById('current-version-num').textContent = d.version;
|
|
43
|
+
document.getElementById('current-version-cmd').textContent = `curl -fsSL ${location.origin}/install | sh`;
|
|
44
|
+
document.getElementById('current-version').hidden = false;
|
|
45
|
+
}
|
|
46
|
+
}).catch(() => {});
|
|
47
|
+
</script>
|
|
48
|
+
<i style="text-align:right">(cc) 4.0 By, Jeff Zucker, 2026</i>
|
|
49
|
+
</body>
|
|
50
|
+
</html>
|