fedipod-server 0.19.0 → 0.21.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/dist/claims.js +3 -2
- package/lib/client/masto/render.mjs +1 -1
- package/lib/core/publisher/index.mjs +50 -11
- package/lib/core/social.mjs +3 -0
- package/lib/core/wire.mjs +44 -5
- package/lib/device/admin/surface.mjs +8 -0
- package/lib/device/cli/commands/setup.mjs +1 -0
- package/lib/device/setup.mjs +1 -0
- package/lib/gateway/front-core.mjs +25 -3
- package/lib/server/embed.mjs +1 -0
- package/package.json +1 -1
- package/run-agent.mjs +4 -0
- package/web/app/agent.mjs +1 -0
- package/web/app/dist/boot.js +1 -0
- package/web/app/dist/boot.js.map +2 -2
- package/web/app/dist/sw.js +91 -14
- package/web/app/dist/sw.js.map +3 -3
- package/web/app/signup.mjs +1 -0
- package/web/app/site/_redirects +1 -0
- package/web/app/site/boot.js +1 -0
- package/web/app/site/sw.js +375 -83
- package/web/front/admin.js +5 -1
package/dist/claims.js
CHANGED
|
@@ -19,7 +19,8 @@ function claims(input, frontHost) {
|
|
|
19
19
|
const bare = String(input.host).split(':')[0].toLowerCase();
|
|
20
20
|
if (bare !== String(frontHost).toLowerCase())
|
|
21
21
|
return false; // a pod subdomain → not ours
|
|
22
|
-
return FRONT_PATHS.has(input.pathname) || input.pathname.startsWith('/u/')
|
|
22
|
+
return FRONT_PATHS.has(input.pathname) || input.pathname.startsWith('/u/')
|
|
23
|
+
|| input.pathname.startsWith('/@'); // the short profile address
|
|
23
24
|
}
|
|
24
25
|
// What an identity answers on its own pod's origin: the protocol routes other
|
|
25
26
|
// software addresses it by, and the one path its owner's pages live under.
|
|
@@ -35,7 +36,7 @@ const AGENT_PATHS = new Set([
|
|
|
35
36
|
// at this name before, so no pod resource is displaced.
|
|
36
37
|
'/.well-known/oauth-authorization-server',
|
|
37
38
|
]);
|
|
38
|
-
const AGENT_PREFIXES = ['/api/', '/oauth/'];
|
|
39
|
+
const AGENT_PREFIXES = ['/api/', '/oauth/', '/@']; // /@handle: the short profile address
|
|
39
40
|
// An identity's inbox is the container `/<root>/ap/inbox/`, matched by shape
|
|
40
41
|
// rather than a fixed root. A POST here is a delivery, verified at the door
|
|
41
42
|
// before it is written; the handler checks the exact path per identity. The
|
|
@@ -46,7 +46,7 @@ export function account(api, actorUrl, { selfAcct } = {}) {
|
|
|
46
46
|
// asking about itself would be told it was a person.
|
|
47
47
|
bot: false, discoverable: true,
|
|
48
48
|
group: self ? api.store.getConfig()?.kind === 'group' : cached.type === 'Group',
|
|
49
|
-
created_at: '2026-01-01T00:00:00.000Z',
|
|
49
|
+
created_at: (self && api.store.getConfig()?.createdAt) || '2026-01-01T00:00:00.000Z',
|
|
50
50
|
note: (self ? api.store.getConfig()?.summary : cached.summary) || '',
|
|
51
51
|
url: actorUrl, uri: actorUrl,
|
|
52
52
|
avatar: selfIcon(api, self, cached) || TRANSPARENT_PNG,
|
|
@@ -118,6 +118,52 @@ export class Publisher {
|
|
|
118
118
|
});
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
+
// The human half: a page a browser can open and follow from. The actor
|
|
122
|
+
// document is for servers; this is the address you hand to a person. It
|
|
123
|
+
// shows who the account is, its fields, when it joined and what it pinned;
|
|
124
|
+
// the counts and the posts live in the collections. Written only when its
|
|
125
|
+
// content changed.
|
|
126
|
+
async publishProfilePage({ force = false } = {}) {
|
|
127
|
+
const { urls } = this;
|
|
128
|
+
const host = new URL(urls.base).host;
|
|
129
|
+
// When the account was made. Accounts from before this was recorded take
|
|
130
|
+
// the date of their oldest post, or today.
|
|
131
|
+
if (!this.config.createdAt) {
|
|
132
|
+
const oldest = this.store.getStatuses().filter(s => s.kind === 'post' && s.published)
|
|
133
|
+
.map(s => s.published).sort()[0];
|
|
134
|
+
this.config.createdAt = oldest || new Date().toISOString();
|
|
135
|
+
this.store.setConfig?.({ ...this.store.getConfig?.(), createdAt: this.config.createdAt });
|
|
136
|
+
}
|
|
137
|
+
const pinned = this.store.getStatuses()
|
|
138
|
+
.filter(s => s.kind === 'post' && s.pinned && s.visibility !== 'private' && s.visibility !== 'direct')
|
|
139
|
+
.sort((a, b) => String(b.published).localeCompare(String(a.published)))
|
|
140
|
+
.slice(0, 5)
|
|
141
|
+
.map(s => ({ content: s.content, published: s.published, url: s.noteId }));
|
|
142
|
+
// The address as the world knows it: at the Gateway for a fronted
|
|
143
|
+
// identity, on the pod's host otherwise, the actor id where no host can
|
|
144
|
+
// answer for a handle.
|
|
145
|
+
const frontActor = this.config.gateway?.frontActor;
|
|
146
|
+
const address = frontActor ? `@${wire.publicHandle(this.config)}@${new URL(frontActor).host}`
|
|
147
|
+
: (wire.webfingerHost(urls.base) ? `@${this.config.handle}@${host}` : urls.actor);
|
|
148
|
+
const html = wire.profilePageHtml({
|
|
149
|
+
name: this.config.name || this.config.handle,
|
|
150
|
+
address,
|
|
151
|
+
summary: this.config.summary ? wire.contentHtml(this.config.summary) : null,
|
|
152
|
+
icon: this.config.icon || null,
|
|
153
|
+
image: this.config.image || null,
|
|
154
|
+
fields: this.config.fields || [],
|
|
155
|
+
joined: this.config.createdAt,
|
|
156
|
+
pinned,
|
|
157
|
+
kind: this.config.kind,
|
|
158
|
+
});
|
|
159
|
+
const digest = crypto.createHash('sha256').update(html).digest('hex').slice(0, 32);
|
|
160
|
+
const seen = this.store.read('published.json', {});
|
|
161
|
+
if (!force && seen.pageDigest === digest) return false;
|
|
162
|
+
await podActor.writeProfilePage(this.remote, urls, html);
|
|
163
|
+
this.store.write('published.json', { ...this.store.read('published.json', {}), pageDigest: digest });
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
|
|
121
167
|
async publishProfile({ force = false } = {}) {
|
|
122
168
|
const { urls } = this;
|
|
123
169
|
const host = new URL(urls.base).host;
|
|
@@ -137,13 +183,16 @@ export class Publisher {
|
|
|
137
183
|
quiesced: !!this.config.quiescedAt, version: AGENT_VERSION,
|
|
138
184
|
moderators: this.config.moderators || [],
|
|
139
185
|
})).digest('hex').slice(0, 32);
|
|
186
|
+
// The human page has its own gate: it changes with pins and the joined
|
|
187
|
+
// date as well as with the profile, and costs one write when it does.
|
|
188
|
+
await this.publishProfilePage({ force });
|
|
140
189
|
if (!force && this.store.read('published.json', {}).surfaceDigest === surface) {
|
|
141
190
|
this.log('profile unchanged — nothing republished');
|
|
142
191
|
return { unreachable: [], updated: 0, skipped: true };
|
|
143
192
|
}
|
|
144
193
|
|
|
145
194
|
await discovery.writeWebfinger(this.remote, urls,
|
|
146
|
-
wire.jrd({ handle: this.config.handle, host, actor: urls.actor }));
|
|
195
|
+
wire.jrd({ handle: this.config.handle, host, actor: urls.actor, page: urls.profileHtml }));
|
|
147
196
|
await discovery.writeHostMeta(this.remote, urls, wire.hostMeta(urls.base));
|
|
148
197
|
|
|
149
198
|
const nodeinfoDocUrl = urls.home + 'ap/nodeinfo-2.0';
|
|
@@ -166,16 +215,6 @@ export class Publisher {
|
|
|
166
215
|
// what concerns this identity. Written only while a gateway is advertised.
|
|
167
216
|
if (gwActive) await this.publishGatewayPolicy();
|
|
168
217
|
|
|
169
|
-
// The human half: a page a browser can open and follow from. The actor
|
|
170
|
-
// document is for servers; this is the address you hand to a person.
|
|
171
|
-
await podActor.writeProfilePage(this.remote, urls, wire.profilePageHtml({
|
|
172
|
-
name: this.config.name || this.config.handle,
|
|
173
|
-
address: wire.webfingerHost(urls.base) ? `@${this.config.handle}@${host}` : urls.actor,
|
|
174
|
-
summary: this.config.summary ? wire.contentHtml(this.config.summary) : null,
|
|
175
|
-
icon: this.config.icon || null,
|
|
176
|
-
kind: this.config.kind,
|
|
177
|
-
}));
|
|
178
|
-
|
|
179
218
|
// WebID → actor: the profile card lists the actor as a foaf:account.
|
|
180
219
|
// Best-effort — a profile that cannot be read or edited does not stop the
|
|
181
220
|
// publish, it is logged and the rest of the surface still goes up.
|
package/lib/core/social.mjs
CHANGED
|
@@ -523,6 +523,9 @@ export async function pinStatus(agent, s, pinned) {
|
|
|
523
523
|
if (s.actor !== agent.publisher.urls.actor) throw new Error('not your status');
|
|
524
524
|
const updated = agent.store.updateStatus(s.noteId, { pinned: !!pinned });
|
|
525
525
|
await agent.publisher.publishFeatured().catch(e => agent.log?.(`featured: ${e.message}`));
|
|
526
|
+
if (agent.publisher.publishProfilePage) {
|
|
527
|
+
await agent.publisher.publishProfilePage().catch(e => agent.log?.(`profile page: ${e.message}`));
|
|
528
|
+
}
|
|
526
529
|
agent.publisher.publishProfile?.().catch(() => {});
|
|
527
530
|
return updated || s;
|
|
528
531
|
}
|
package/lib/core/wire.mjs
CHANGED
|
@@ -52,11 +52,14 @@ export function hostMeta(base) {
|
|
|
52
52
|
return `<?xml version="1.0" encoding="UTF-8"?>\n<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">\n <Link rel="lrdd" template="${base}.well-known/webfinger?resource={uri}"/>\n</XRD>\n`;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
export function jrd({ handle, host, actor, aliases = [] }) {
|
|
55
|
+
export function jrd({ handle, host, actor, aliases = [], page = null }) {
|
|
56
56
|
return {
|
|
57
57
|
subject: `acct:${handle}@${host}`,
|
|
58
58
|
...(aliases.length ? { aliases } : {}),
|
|
59
|
-
links: [
|
|
59
|
+
links: [
|
|
60
|
+
{ rel: 'self', type: 'application/activity+json', href: actor },
|
|
61
|
+
...(page ? [{ rel: 'http://webfinger.net/rel/profile-page', type: 'text/html', href: page }] : []),
|
|
62
|
+
],
|
|
60
63
|
};
|
|
61
64
|
}
|
|
62
65
|
|
|
@@ -193,9 +196,24 @@ export function actorDoc({ urls, handle, name, publicKeyPem, assertionKey = null
|
|
|
193
196
|
// the actor document. It says who this is and how to follow — the handle to
|
|
194
197
|
// paste, and the remote-follow control that hands the visitor to their own
|
|
195
198
|
// server's follow screen.
|
|
196
|
-
|
|
199
|
+
// `fields` are the profile's name/value rows, `joined` an ISO date, `pinned`
|
|
200
|
+
// the pinned posts as { content (sanitized HTML), published, url }.
|
|
201
|
+
export function profilePageHtml({ name, address, summary = null, icon = null, image = null,
|
|
202
|
+
fields = [], joined = null, pinned = [], kind = 'person' }) {
|
|
197
203
|
const esc = (s) => String(s ?? '').replace(/[&<>"]/g, c => ({ ...HTML_ESCAPES, '"': '"' }[c]));
|
|
198
204
|
const what = kind === 'group' ? 'a group' : 'an account';
|
|
205
|
+
const month = (iso) => {
|
|
206
|
+
const d = new Date(iso);
|
|
207
|
+
return Number.isNaN(d.getTime()) ? null
|
|
208
|
+
: d.toLocaleDateString('en', { month: 'long', year: 'numeric', timeZone: 'UTC' });
|
|
209
|
+
};
|
|
210
|
+
const day = (iso) => {
|
|
211
|
+
const d = new Date(iso);
|
|
212
|
+
return Number.isNaN(d.getTime()) ? '' : d.toISOString().slice(0, 10);
|
|
213
|
+
};
|
|
214
|
+
const fieldRows = fields.filter(f => f?.name && f?.value !== undefined && f?.value !== null && String(f.value) !== '');
|
|
215
|
+
const joinedText = joined ? month(joined) : null;
|
|
216
|
+
const pins = pinned.filter(x => x && typeof x.content === 'string');
|
|
199
217
|
return `<!doctype html>
|
|
200
218
|
<html lang="en">
|
|
201
219
|
<head>
|
|
@@ -205,14 +223,23 @@ export function profilePageHtml({ name, address, summary = null, icon = null, ki
|
|
|
205
223
|
<style>
|
|
206
224
|
:root { color-scheme: light dark; }
|
|
207
225
|
body { font: 112.5%/1.5 system-ui, sans-serif; max-width: 34rem; margin: 3rem auto; padding: 0 1rem; }
|
|
208
|
-
img.
|
|
226
|
+
img.header { width: 100%; max-height: 12rem; object-fit: cover; border-radius: 1rem; }
|
|
227
|
+
img.avatar { width: 6rem; height: 6rem; border-radius: 1rem; object-fit: cover; margin-top: 1rem; }
|
|
209
228
|
h1 { margin: .5rem 0 0; }
|
|
210
229
|
.address { font-size: 1.1rem; user-select: all; }
|
|
230
|
+
dl.fields { display: grid; grid-template-columns: max-content 1fr; gap: .4rem 1rem; margin: 1.5rem 0; }
|
|
231
|
+
dl.fields dt { font-weight: 600; }
|
|
232
|
+
dl.fields dd { margin: 0; overflow-wrap: anywhere; }
|
|
233
|
+
.joined { margin: 1rem 0; }
|
|
234
|
+
section.pinned h2 { font-size: 1.1rem; margin: 2rem 0 .5rem; }
|
|
235
|
+
article.pin { border: 1px solid #8884; border-radius: 1rem; padding: 1rem 1.2rem; margin: 1rem 0; }
|
|
236
|
+
article.pin p:last-child { margin-bottom: 0; }
|
|
237
|
+
article.pin .when { font-size: 1rem; }
|
|
211
238
|
form { margin-top: 2rem; }
|
|
212
239
|
label { display: block; margin-bottom: .3rem; }
|
|
213
240
|
input { font: inherit; padding: .5rem; width: 14rem; max-width: 100%; }
|
|
214
241
|
button { font: inherit; padding: .5rem 1rem; }
|
|
215
|
-
.hint { color: #
|
|
242
|
+
.hint { color: #666; font-size: 1rem; }
|
|
216
243
|
.err { color: #b00020; }
|
|
217
244
|
.err:empty { display: none; }
|
|
218
245
|
@media (prefers-color-scheme: dark) {
|
|
@@ -223,10 +250,22 @@ button { font: inherit; padding: .5rem 1rem; }
|
|
|
223
250
|
</head>
|
|
224
251
|
<body>
|
|
225
252
|
<main>
|
|
253
|
+
${image ? `<img class="header" src="${esc(image)}" alt="">` : ''}
|
|
226
254
|
${icon ? `<img class="avatar" src="${esc(icon)}" alt="">` : ''}
|
|
227
255
|
<h1>${esc(name)}</h1>
|
|
228
256
|
<p class="address">${esc(address)}</p>
|
|
229
257
|
${summary ? `<div>${summary}</div>` : ''}
|
|
258
|
+
${fieldRows.length ? `<dl class="fields">
|
|
259
|
+
${fieldRows.map(f => `<dt>${esc(f.name)}</dt><dd>${esc(f.value)}</dd>`).join('\n')}
|
|
260
|
+
</dl>` : ''}
|
|
261
|
+
${joinedText ? `<p class="joined">Joined ${esc(joinedText)}</p>` : ''}
|
|
262
|
+
${pins.length ? `<section class="pinned">
|
|
263
|
+
<h2>Pinned</h2>
|
|
264
|
+
${pins.map(x => `<article class="pin">
|
|
265
|
+
${x.content}
|
|
266
|
+
<p class="when">${x.url ? `<a href="${esc(x.url)}">${esc(day(x.published))}</a>` : esc(day(x.published))}</p>
|
|
267
|
+
</article>`).join('\n')}
|
|
268
|
+
</section>` : ''}
|
|
230
269
|
<p>This is ${what} on the Fediverse. To follow it, paste the address above
|
|
231
270
|
into the search box of Mastodon or any Fediverse app — or use the form.</p>
|
|
232
271
|
<form id="follow">
|
|
@@ -181,6 +181,14 @@ export function buildAdminSurface({ agent, gate, allowed, log = console.log,
|
|
|
181
181
|
const scheme = req.socket.encrypted || req.headers['x-forwarded-proto'] === 'https' ? 'https' : 'http';
|
|
182
182
|
return json(res, 200, masto.authorizationServerMetadata(`${scheme}://${req.headers.host}${mount}`));
|
|
183
183
|
}
|
|
184
|
+
// The short address a person types: /@handle opens the profile page.
|
|
185
|
+
const at = /^\/@([^/]+)$/u.exec(p);
|
|
186
|
+
const ownHandle = handle || agent.store?.getConfig?.()?.handle || '';
|
|
187
|
+
if (at && ownHandle && agent.publisher?.urls?.profileHtml
|
|
188
|
+
&& decodeURIComponent(at[1]).toLowerCase() === String(ownHandle).toLowerCase()) {
|
|
189
|
+
res.writeHead(302, { location: agent.publisher.urls.profileHtml, 'cache-control': 'no-store' });
|
|
190
|
+
res.end(); return;
|
|
191
|
+
}
|
|
184
192
|
if (atDoor && gate(req, res)) return;
|
|
185
193
|
if (p === '/api/v1/streaming/health') {
|
|
186
194
|
res.writeHead(200, { 'content-type': 'text/plain' }); res.end('OK'); return;
|
|
@@ -163,6 +163,7 @@ const credential = await mintCredential({ origin: issuer, email, password, name:
|
|
|
163
163
|
const rec = {
|
|
164
164
|
...credential,
|
|
165
165
|
remotePod: pod.endsWith('/') ? pod : pod + '/',
|
|
166
|
+
createdAt: new Date().toISOString(),
|
|
166
167
|
// The private half goes on THIS machine, exactly as the browser setup does
|
|
167
168
|
// (lib/setup.mjs). Omitting it here meant the two paths produced different
|
|
168
169
|
// installs from the same answers: the CLI put the timeline, contacts,
|
package/lib/device/setup.mjs
CHANGED
|
@@ -293,6 +293,7 @@ export async function runSetup({ home, agent, answers, run, deps = {}, log = ()
|
|
|
293
293
|
const rec = {
|
|
294
294
|
...credential,
|
|
295
295
|
remotePod: pod.endsWith('/') ? pod : pod + '/',
|
|
296
|
+
createdAt: new Date().toISOString(),
|
|
296
297
|
...(root ? { root } : {}),
|
|
297
298
|
...(keys === 'pod' ? { keysMode: 'pod' } : {}),
|
|
298
299
|
// Where the private half lives. Per-machine, like keysMode.
|
|
@@ -26,11 +26,17 @@ import { podBaseOfWebId } from '../pod/urls.mjs';
|
|
|
26
26
|
// wire.mjs: wire drags the agent's whole HTML pipeline (sanitize-html and
|
|
27
27
|
// friends), which a serverless front must never carry — it crashed the
|
|
28
28
|
// deployed function before it answered its first request.
|
|
29
|
-
const jrd = ({ handle, host, actor, aliases = [] }) => ({
|
|
29
|
+
const jrd = ({ handle, host, actor, aliases = [], page = null }) => ({
|
|
30
30
|
subject: `acct:${handle}@${host}`,
|
|
31
31
|
...(aliases.length ? { aliases } : {}),
|
|
32
|
-
links: [
|
|
32
|
+
links: [
|
|
33
|
+
{ rel: 'self', type: 'application/activity+json', href: actor },
|
|
34
|
+
...(page ? [{ rel: 'http://webfinger.net/rel/profile-page', type: 'text/html', href: page }] : []),
|
|
35
|
+
],
|
|
33
36
|
});
|
|
37
|
+
// The public profile page of any account the front answers for lives on
|
|
38
|
+
// its pod, whether the address is the front's or the pod's own.
|
|
39
|
+
const profilePageOf = (rec) => (rec?.podHome ? rec.podHome + 'ap/profile.html' : null);
|
|
34
40
|
|
|
35
41
|
const AP_CT = 'application/activity+json';
|
|
36
42
|
|
|
@@ -600,13 +606,29 @@ async function route(request, ctx) {
|
|
|
600
606
|
// is the alias, so a client signing in by the fronted address can find
|
|
601
607
|
// the pod (and its login) without a lookup only the host could answer.
|
|
602
608
|
const podActor = rec.inboxOnly ? [] : [rec.podHome + 'ap/actor'];
|
|
603
|
-
const out = j(200, jrd({ handle: m[1], host: ctx.host, actor: rec.actorUrl, aliases: podActor }),
|
|
609
|
+
const out = j(200, jrd({ handle: m[1], host: ctx.host, actor: rec.actorUrl, aliases: podActor, page: profilePageOf(rec) }),
|
|
604
610
|
'application/jrd+json');
|
|
605
611
|
// RFC 7033: a JRD is read from any origin.
|
|
606
612
|
out.headers['access-control-allow-origin'] = '*';
|
|
607
613
|
return out;
|
|
608
614
|
}
|
|
609
615
|
|
|
616
|
+
// The short address a person types: https://<host>/@handle opens the
|
|
617
|
+
// profile page, the way most Fediverse servers spell it. /@handle@pod-host
|
|
618
|
+
// is the same for an account whose address is its pod's, which the front
|
|
619
|
+
// holds under that full address — the form other servers use for a
|
|
620
|
+
// profile that lives elsewhere.
|
|
621
|
+
const at = /^\/@([^/@]+)(?:@([^/@]+))?$/u.exec(pathname);
|
|
622
|
+
if (at) {
|
|
623
|
+
const handle = decodeURIComponent(at[1]).toLowerCase();
|
|
624
|
+
const host = at[2] ? decodeURIComponent(at[2]).toLowerCase() : null;
|
|
625
|
+
const key = host && host !== String(ctx.host).toLowerCase() ? `${handle}@${host}` : handle;
|
|
626
|
+
const rec = await ctx.lookup(key);
|
|
627
|
+
const page = rec && profilePageOf(rec);
|
|
628
|
+
if (!page) return notFound();
|
|
629
|
+
return { status: 302, headers: { location: page, 'cache-control': 'no-store' }, body: '' };
|
|
630
|
+
}
|
|
631
|
+
|
|
610
632
|
const up = parseUserPath(pathname);
|
|
611
633
|
if (!up) return notFound();
|
|
612
634
|
const rec = await ctx.lookup(up.handle);
|
package/lib/server/embed.mjs
CHANGED
package/package.json
CHANGED
package/run-agent.mjs
CHANGED
|
@@ -588,6 +588,10 @@ export class Agent {
|
|
|
588
588
|
if (repair) {
|
|
589
589
|
this.ensureActorPublished()
|
|
590
590
|
.catch(e => this.log(`actor check failed: ${e.message}`));
|
|
591
|
+
// The human page, rewritten only when what it shows has changed: one
|
|
592
|
+
// local digest compare, and a write the first time after an upgrade.
|
|
593
|
+
this.publisher.publishProfilePage()
|
|
594
|
+
.catch(e => this.log(`profile page: ${e.message}`));
|
|
591
595
|
}
|
|
592
596
|
this.seedFollowNotifications().catch(e => this.log(`notification seeding failed: ${e.message}`));
|
|
593
597
|
}
|
package/web/app/agent.mjs
CHANGED
|
@@ -88,6 +88,7 @@ export class BrowserAgent {
|
|
|
88
88
|
// Own posts the outbox names and the timeline index lacks come back
|
|
89
89
|
// here, before anything acts on the index.
|
|
90
90
|
await this.publisher.healStatuses().catch((e) => this.log(`healing the timeline index: ${e.message}`));
|
|
91
|
+
await this.publisher.publishProfilePage().catch((e) => this.log(`profile page: ${e.message}`));
|
|
91
92
|
// And start delivering again, since demote() stopped it. startQueue() is
|
|
92
93
|
// idempotent, so a goActive() that was already active costs nothing.
|
|
93
94
|
this.deliverer?.startQueue?.();
|
package/web/app/dist/boot.js
CHANGED
|
@@ -33444,6 +33444,7 @@ async function signUp(answers, { onStep = () => {
|
|
|
33444
33444
|
handle,
|
|
33445
33445
|
name: handle,
|
|
33446
33446
|
issuer: credential.issuerOrigin,
|
|
33447
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
33447
33448
|
...gateway ? { gateway } : {}
|
|
33448
33449
|
};
|
|
33449
33450
|
const cfgRemote = new BrowserRemotePod(session, { webId: credential.webId, role: "signup", log: () => {
|