@geekity/cms 0.3.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/LICENSE +21 -0
- package/README.md +870 -0
- package/admin/layouts/_flash.njk +5 -0
- package/admin/layouts/base.njk +27 -0
- package/admin/layouts/dashboard.njk +78 -0
- package/admin/layouts/document-conflict.njk +49 -0
- package/admin/layouts/document-editor.njk +167 -0
- package/admin/layouts/document-list.njk +92 -0
- package/admin/layouts/federation.njk +145 -0
- package/admin/layouts/login.njk +23 -0
- package/admin/layouts/placeholder.njk +10 -0
- package/admin/layouts/settings.njk +116 -0
- package/admin/layouts/setup.njk +27 -0
- package/admin/layouts/shell.njk +37 -0
- package/admin/layouts/users.njk +91 -0
- package/admin/static/admin.css +726 -0
- package/admin/static/editor.js +30 -0
- package/dist/admin/assets.d.ts +26 -0
- package/dist/admin/assets.d.ts.map +1 -0
- package/dist/admin/assets.js +38 -0
- package/dist/admin/assets.js.map +1 -0
- package/dist/admin/credentials.d.ts +28 -0
- package/dist/admin/credentials.d.ts.map +1 -0
- package/dist/admin/credentials.js +39 -0
- package/dist/admin/credentials.js.map +1 -0
- package/dist/admin/documents.d.ts +134 -0
- package/dist/admin/documents.d.ts.map +1 -0
- package/dist/admin/documents.js +613 -0
- package/dist/admin/documents.js.map +1 -0
- package/dist/admin/federation.d.ts +194 -0
- package/dist/admin/federation.d.ts.map +1 -0
- package/dist/admin/federation.js +299 -0
- package/dist/admin/federation.js.map +1 -0
- package/dist/admin/flash.d.ts +24 -0
- package/dist/admin/flash.d.ts.map +1 -0
- package/dist/admin/flash.js +31 -0
- package/dist/admin/flash.js.map +1 -0
- package/dist/admin/index.d.ts +23 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +15 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/passwords.d.ts +49 -0
- package/dist/admin/passwords.d.ts.map +1 -0
- package/dist/admin/passwords.js +122 -0
- package/dist/admin/passwords.js.map +1 -0
- package/dist/admin/preview.d.ts +18 -0
- package/dist/admin/preview.d.ts.map +1 -0
- package/dist/admin/preview.js +83 -0
- package/dist/admin/preview.js.map +1 -0
- package/dist/admin/routes.d.ts +54 -0
- package/dist/admin/routes.d.ts.map +1 -0
- package/dist/admin/routes.js +383 -0
- package/dist/admin/routes.js.map +1 -0
- package/dist/admin/session.d.ts +44 -0
- package/dist/admin/session.d.ts.map +1 -0
- package/dist/admin/session.js +69 -0
- package/dist/admin/session.js.map +1 -0
- package/dist/admin/settings.d.ts +194 -0
- package/dist/admin/settings.d.ts.map +1 -0
- package/dist/admin/settings.js +470 -0
- package/dist/admin/settings.js.map +1 -0
- package/dist/admin/store.d.ts +407 -0
- package/dist/admin/store.d.ts.map +1 -0
- package/dist/admin/store.js +704 -0
- package/dist/admin/store.js.map +1 -0
- package/dist/admin/templates.d.ts +36 -0
- package/dist/admin/templates.d.ts.map +1 -0
- package/dist/admin/templates.js +43 -0
- package/dist/admin/templates.js.map +1 -0
- package/dist/admin/uploads.d.ts +96 -0
- package/dist/admin/uploads.d.ts.map +1 -0
- package/dist/admin/uploads.js +179 -0
- package/dist/admin/uploads.js.map +1 -0
- package/dist/admin/users.d.ts +95 -0
- package/dist/admin/users.d.ts.map +1 -0
- package/dist/admin/users.js +233 -0
- package/dist/admin/users.js.map +1 -0
- package/dist/cli.d.ts +85 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +424 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +172 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +184 -0
- package/dist/config.js.map +1 -0
- package/dist/content/document.d.ts +66 -0
- package/dist/content/document.d.ts.map +1 -0
- package/dist/content/document.js +13 -0
- package/dist/content/document.js.map +1 -0
- package/dist/content/index.d.ts +17 -0
- package/dist/content/index.d.ts.map +1 -0
- package/dist/content/index.js +10 -0
- package/dist/content/index.js.map +1 -0
- package/dist/content/markdown.d.ts +3 -0
- package/dist/content/markdown.d.ts.map +1 -0
- package/dist/content/markdown.js +39 -0
- package/dist/content/markdown.js.map +1 -0
- package/dist/content/media.d.ts +65 -0
- package/dist/content/media.d.ts.map +1 -0
- package/dist/content/media.js +143 -0
- package/dist/content/media.js.map +1 -0
- package/dist/content/parser.d.ts +24 -0
- package/dist/content/parser.d.ts.map +1 -0
- package/dist/content/parser.js +165 -0
- package/dist/content/parser.js.map +1 -0
- package/dist/content/save.d.ts +60 -0
- package/dist/content/save.d.ts.map +1 -0
- package/dist/content/save.js +103 -0
- package/dist/content/save.js.map +1 -0
- package/dist/content/slug.d.ts +25 -0
- package/dist/content/slug.d.ts.map +1 -0
- package/dist/content/slug.js +75 -0
- package/dist/content/slug.js.map +1 -0
- package/dist/content/store.d.ts +133 -0
- package/dist/content/store.d.ts.map +1 -0
- package/dist/content/store.js +411 -0
- package/dist/content/store.js.map +1 -0
- package/dist/content/sync.d.ts +146 -0
- package/dist/content/sync.d.ts.map +1 -0
- package/dist/content/sync.js +421 -0
- package/dist/content/sync.js.map +1 -0
- package/dist/content/writer.d.ts +37 -0
- package/dist/content/writer.d.ts.map +1 -0
- package/dist/content/writer.js +97 -0
- package/dist/content/writer.js.map +1 -0
- package/dist/env.d.ts +48 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +2 -0
- package/dist/env.js.map +1 -0
- package/dist/federation/actor.d.ts +55 -0
- package/dist/federation/actor.d.ts.map +1 -0
- package/dist/federation/actor.js +101 -0
- package/dist/federation/actor.js.map +1 -0
- package/dist/federation/article.d.ts +83 -0
- package/dist/federation/article.d.ts.map +1 -0
- package/dist/federation/article.js +169 -0
- package/dist/federation/article.js.map +1 -0
- package/dist/federation/delivery.d.ts +90 -0
- package/dist/federation/delivery.d.ts.map +1 -0
- package/dist/federation/delivery.js +289 -0
- package/dist/federation/delivery.js.map +1 -0
- package/dist/federation/federation.d.ts +75 -0
- package/dist/federation/federation.d.ts.map +1 -0
- package/dist/federation/federation.js +190 -0
- package/dist/federation/federation.js.map +1 -0
- package/dist/federation/followers.d.ts +43 -0
- package/dist/federation/followers.d.ts.map +1 -0
- package/dist/federation/followers.js +69 -0
- package/dist/federation/followers.js.map +1 -0
- package/dist/federation/inbox.d.ts +68 -0
- package/dist/federation/inbox.d.ts.map +1 -0
- package/dist/federation/inbox.js +171 -0
- package/dist/federation/inbox.js.map +1 -0
- package/dist/federation/index.d.ts +14 -0
- package/dist/federation/index.d.ts.map +1 -0
- package/dist/federation/index.js +10 -0
- package/dist/federation/index.js.map +1 -0
- package/dist/federation/keys.d.ts +27 -0
- package/dist/federation/keys.d.ts.map +1 -0
- package/dist/federation/keys.js +60 -0
- package/dist/federation/keys.js.map +1 -0
- package/dist/federation/mount.d.ts +23 -0
- package/dist/federation/mount.d.ts.map +1 -0
- package/dist/federation/mount.js +71 -0
- package/dist/federation/mount.js.map +1 -0
- package/dist/federation/paths.d.ts +85 -0
- package/dist/federation/paths.d.ts.map +1 -0
- package/dist/federation/paths.js +97 -0
- package/dist/federation/paths.js.map +1 -0
- package/dist/index.d.ts +106 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +148 -0
- package/dist/index.js.map +1 -0
- package/dist/init.d.ts +71 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/init.js +125 -0
- package/dist/init.js.map +1 -0
- package/dist/web/assets.d.ts +65 -0
- package/dist/web/assets.d.ts.map +1 -0
- package/dist/web/assets.js +152 -0
- package/dist/web/assets.js.map +1 -0
- package/dist/web/context.d.ts +121 -0
- package/dist/web/context.d.ts.map +1 -0
- package/dist/web/context.js +112 -0
- package/dist/web/context.js.map +1 -0
- package/dist/web/documents.d.ts +28 -0
- package/dist/web/documents.d.ts.map +1 -0
- package/dist/web/documents.js +39 -0
- package/dist/web/documents.js.map +1 -0
- package/dist/web/feeds.d.ts +122 -0
- package/dist/web/feeds.d.ts.map +1 -0
- package/dist/web/feeds.js +229 -0
- package/dist/web/feeds.js.map +1 -0
- package/dist/web/index.d.ts +17 -0
- package/dist/web/index.d.ts.map +1 -0
- package/dist/web/index.js +10 -0
- package/dist/web/index.js.map +1 -0
- package/dist/web/negotiate.d.ts +219 -0
- package/dist/web/negotiate.d.ts.map +1 -0
- package/dist/web/negotiate.js +409 -0
- package/dist/web/negotiate.js.map +1 -0
- package/dist/web/pagination.d.ts +48 -0
- package/dist/web/pagination.d.ts.map +1 -0
- package/dist/web/pagination.js +30 -0
- package/dist/web/pagination.js.map +1 -0
- package/dist/web/render.d.ts +72 -0
- package/dist/web/render.d.ts.map +1 -0
- package/dist/web/render.js +73 -0
- package/dist/web/render.js.map +1 -0
- package/dist/web/routes.d.ts +31 -0
- package/dist/web/routes.d.ts.map +1 -0
- package/dist/web/routes.js +366 -0
- package/dist/web/routes.js.map +1 -0
- package/dist/web/templates.d.ts +34 -0
- package/dist/web/templates.d.ts.map +1 -0
- package/dist/web/templates.js +106 -0
- package/dist/web/templates.js.map +1 -0
- package/package.json +68 -0
- package/templates/site/content/_data/site.json +8 -0
- package/templates/site/content/pages/about.md +11 -0
- package/templates/site/content/pages/pages.json +3 -0
- package/templates/site/content/posts/2026-01-01-hello-world.md +34 -0
- package/templates/site/content/posts/posts.json +4 -0
- package/templates/site/geekity.config.ts +11 -0
- package/templates/site/gitignore +3 -0
- package/templates/site/server.ts +20 -0
- package/templates/site/tsconfig.json +18 -0
- package/themes/default/README.md +148 -0
- package/themes/default/layouts/404.njk +9 -0
- package/themes/default/layouts/base.njk +59 -0
- package/themes/default/layouts/home.njk +9 -0
- package/themes/default/layouts/page.njk +13 -0
- package/themes/default/layouts/post.njk +30 -0
- package/themes/default/layouts/tag.njk +19 -0
- package/themes/default/partials/pagination.njk +14 -0
- package/themes/default/partials/post-list.njk +24 -0
- package/themes/default/partials/tags.njk +12 -0
- package/themes/default/static/style.css +327 -0
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import { Activity, getTypeId, PUBLIC_COLLECTION, Update } from '@fedify/vocab';
|
|
2
|
+
import { readSiteSettings } from '../admin/settings.js';
|
|
3
|
+
import { saveDocument } from '../content/save.js';
|
|
4
|
+
import { documentContent } from '../content/writer.js';
|
|
5
|
+
import { siteActor } from './actor.js';
|
|
6
|
+
import { articleObjectId, isFederatedDocument, postCreateActivity, postDeleteActivity, postUpdateActivity, } from './article.js';
|
|
7
|
+
import { followerRecipient } from './followers.js';
|
|
8
|
+
import { SITE_ACTOR_IDENTIFIER } from './keys.js';
|
|
9
|
+
import { postObjectId, updateActivityId } from './paths.js';
|
|
10
|
+
/**
|
|
11
|
+
* Build the delivery service for one site.
|
|
12
|
+
*
|
|
13
|
+
* doc-4's delivery table is the whole of the policy: a post that becomes
|
|
14
|
+
* published is a `Create`, an edit to a published post is an `Update`, and a
|
|
15
|
+
* post that stops being published — draft, trashed or deleted, which are one
|
|
16
|
+
* thing from outside — is a `Delete` of a `Tombstone`.
|
|
17
|
+
*
|
|
18
|
+
* Nothing is delivered for a full scan. The boot scan reports a cold index as
|
|
19
|
+
* a directory full of creations, so federating them would announce the whole
|
|
20
|
+
* archive to every follower every time somebody deleted the database.
|
|
21
|
+
*/
|
|
22
|
+
export function createDeliveryService(options) {
|
|
23
|
+
const { federation, admin, store, config } = options;
|
|
24
|
+
const logger = options.logger ?? console;
|
|
25
|
+
/**
|
|
26
|
+
* Whether a delivery is over by the time `sendActivity` resolves.
|
|
27
|
+
*
|
|
28
|
+
* With no queue it is: Fedify posts to each inbox and waits. With one, all
|
|
29
|
+
* that is known is that the activity was accepted for delivery, and the
|
|
30
|
+
* queue retries out of band without reporting back — which is the difference
|
|
31
|
+
* between a `sent` row and a `queued` one.
|
|
32
|
+
*/
|
|
33
|
+
const synchronous = config.federation.queue === null;
|
|
34
|
+
// Deliveries are chained rather than run in parallel, and the chain never
|
|
35
|
+
// rejects: a failure is recorded against the followers it happened to and
|
|
36
|
+
// the next activity still goes out.
|
|
37
|
+
let chain = Promise.resolve();
|
|
38
|
+
function enqueue(task) {
|
|
39
|
+
const result = chain.then(task);
|
|
40
|
+
chain = result.then(ignore, ignore);
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
/** A context outside any request, which is where a delivery happens from. */
|
|
44
|
+
function deliveryContext() {
|
|
45
|
+
return federation.createContext(new URL(config.baseUrl), { admin, store, config });
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Write `activitypub.id` and `activitypub.published` into the post's file,
|
|
49
|
+
* and answer with the document as it now reads.
|
|
50
|
+
*
|
|
51
|
+
* This happens before the first activity goes out, so the id the follower is
|
|
52
|
+
* handed is the id the file will keep however often the post is renamed
|
|
53
|
+
* afterwards (doc-4). It is written through the same
|
|
54
|
+
* {@link saveDocument} the admin writes through, which corrects the index in
|
|
55
|
+
* the same breath — so the watcher's re-read of the file finds a hash that
|
|
56
|
+
* already matches and this write federates nothing of its own.
|
|
57
|
+
*/
|
|
58
|
+
async function stamp(document) {
|
|
59
|
+
const existing = document.activitypub;
|
|
60
|
+
const id = existing?.id ?? postObjectId(document.slug, config.baseUrl);
|
|
61
|
+
const published = existing?.published ?? document.date ?? new Date().toISOString();
|
|
62
|
+
if (existing?.id === id && existing.published === published)
|
|
63
|
+
return document;
|
|
64
|
+
return await saveDocument({
|
|
65
|
+
contentDir: config.contentDir,
|
|
66
|
+
store,
|
|
67
|
+
path: document.path,
|
|
68
|
+
content: { ...documentContent(document), activitypub: { id, published } },
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Record an activity and fan it out to the followers.
|
|
73
|
+
*
|
|
74
|
+
* The activity is stored before it is sent, not after, so an activity whose
|
|
75
|
+
* delivery is interrupted is still one an admin can re-send.
|
|
76
|
+
*/
|
|
77
|
+
async function send(context, activity, about) {
|
|
78
|
+
const activityId = activity.id?.href;
|
|
79
|
+
if (activityId === undefined) {
|
|
80
|
+
throw new TypeError('An activity cannot be delivered without an id.');
|
|
81
|
+
}
|
|
82
|
+
const objectId = articleObjectId(context, about).href;
|
|
83
|
+
const activityType = typeNameOf(activity);
|
|
84
|
+
const json = await activity.toJsonLd({
|
|
85
|
+
format: 'compact',
|
|
86
|
+
contextLoader: context.contextLoader,
|
|
87
|
+
});
|
|
88
|
+
admin.putOutboundActivity({
|
|
89
|
+
activityId,
|
|
90
|
+
activityType,
|
|
91
|
+
objectId,
|
|
92
|
+
slug: about.slug,
|
|
93
|
+
json: JSON.stringify(json),
|
|
94
|
+
});
|
|
95
|
+
return await fanOut(context, activity, { activityId, activityType, objectId });
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Deliver one activity to every follower, one inbox at a time.
|
|
99
|
+
*
|
|
100
|
+
* Fedify's own `sendActivity(…, 'followers', …)` would reach the same
|
|
101
|
+
* inboxes in one call, but it answers `void`: there would be no way of
|
|
102
|
+
* saying which follower did not get it, which is the thing decision-5
|
|
103
|
+
* promised to record. So the followers are grouped by the inbox they share —
|
|
104
|
+
* one POST still serves a whole instance — and each group's outcome is
|
|
105
|
+
* written against every follower behind it.
|
|
106
|
+
*/
|
|
107
|
+
async function fanOut(context, activity, about) {
|
|
108
|
+
const deliveries = [];
|
|
109
|
+
for (const [inboxId, members] of groupByInbox(admin.listFollowers())) {
|
|
110
|
+
let status = synchronous ? 'sent' : 'queued';
|
|
111
|
+
let error = null;
|
|
112
|
+
try {
|
|
113
|
+
await context.sendActivity({ identifier: SITE_ACTOR_IDENTIFIER }, members.map(followerRecipient), activity,
|
|
114
|
+
// The object id keeps a post's activities in order per server, so a
|
|
115
|
+
// follower cannot be shown an Update of something it has not been
|
|
116
|
+
// told about yet.
|
|
117
|
+
{ preferSharedInbox: true, orderingKey: about.objectId });
|
|
118
|
+
}
|
|
119
|
+
catch (thrown) {
|
|
120
|
+
status = 'failed';
|
|
121
|
+
error = messageOf(thrown);
|
|
122
|
+
logger.warn(`Could not deliver ${about.activityId} to ${inboxId}: ${error}`);
|
|
123
|
+
}
|
|
124
|
+
for (const member of members) {
|
|
125
|
+
deliveries.push(admin.recordDelivery({
|
|
126
|
+
activityId: about.activityId,
|
|
127
|
+
actorId: member.actorId,
|
|
128
|
+
inboxId,
|
|
129
|
+
status,
|
|
130
|
+
error,
|
|
131
|
+
}));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return { ...about, deliveries };
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
async handle(change) {
|
|
138
|
+
// A full scan is a rebuild of the index, not news about the site.
|
|
139
|
+
if (change.origin === 'scan')
|
|
140
|
+
return;
|
|
141
|
+
const before = federatedOrUndefined(change.previous);
|
|
142
|
+
const after = federatedOrUndefined(change.next);
|
|
143
|
+
if (before === undefined && after === undefined)
|
|
144
|
+
return;
|
|
145
|
+
const context = deliveryContext();
|
|
146
|
+
if (after === undefined) {
|
|
147
|
+
// Unpublished, trashed or deleted; `before` is the post as it last
|
|
148
|
+
// stood, and the only version there is to build a Tombstone from.
|
|
149
|
+
if (before === undefined)
|
|
150
|
+
return;
|
|
151
|
+
const deleted = new Date().toISOString();
|
|
152
|
+
queue(() => send(context, postDeleteActivity(context, before, deleted), before));
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
{
|
|
156
|
+
const stamped = await stamp(after);
|
|
157
|
+
if (before === undefined) {
|
|
158
|
+
queue(() => send(context, postCreateActivity(context, stamped), stamped));
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
// Almost always the same id, because the stamped one follows the post
|
|
162
|
+
// through a rename. When it is not — a post federated before this
|
|
163
|
+
// version, renamed since — the old object is withdrawn rather than
|
|
164
|
+
// left standing under an id nothing answers to any more.
|
|
165
|
+
const previousId = articleObjectId(context, before).href;
|
|
166
|
+
if (previousId === articleObjectId(context, stamped).href) {
|
|
167
|
+
queue(() => send(context, postUpdateActivity(context, stamped), stamped));
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const deleted = new Date().toISOString();
|
|
171
|
+
queue(() => send(context, postDeleteActivity(context, before, deleted), before));
|
|
172
|
+
queue(() => send(context, postCreateActivity(context, stamped), stamped));
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
async updateActor() {
|
|
176
|
+
// Building the actor loads — and on a cold database generates — the key
|
|
177
|
+
// pairs, so a site nobody follows does not pay for an activity that has
|
|
178
|
+
// nowhere to go.
|
|
179
|
+
if (admin.countFollowers() === 0)
|
|
180
|
+
return undefined;
|
|
181
|
+
return await enqueue(async () => {
|
|
182
|
+
const context = deliveryContext();
|
|
183
|
+
const actorId = context.getActorUri(SITE_ACTOR_IDENTIFIER);
|
|
184
|
+
const actor = await siteActor(context, SITE_ACTOR_IDENTIFIER, {
|
|
185
|
+
settings: readSiteSettings(admin),
|
|
186
|
+
baseUrl: config.baseUrl,
|
|
187
|
+
});
|
|
188
|
+
// The revision is the moment rather than a hash of the profile: an
|
|
189
|
+
// avatar removed and put back is the same profile twice, and both
|
|
190
|
+
// times the followers have to be told rather than recognise an id
|
|
191
|
+
// they have already seen and skip it.
|
|
192
|
+
const activityId = updateActivityId(actorId, new Date().toISOString());
|
|
193
|
+
const activity = new Update({
|
|
194
|
+
id: activityId,
|
|
195
|
+
actor: actorId,
|
|
196
|
+
object: actor,
|
|
197
|
+
to: PUBLIC_COLLECTION,
|
|
198
|
+
cc: context.getFollowersUri(SITE_ACTOR_IDENTIFIER),
|
|
199
|
+
});
|
|
200
|
+
const about = {
|
|
201
|
+
activityId: activityId.href,
|
|
202
|
+
activityType: 'Update',
|
|
203
|
+
// The object is the actor, not a post, and there is no slug to
|
|
204
|
+
// record: that is what keeps an actor Update out of the federation
|
|
205
|
+
// screen's per-post delivery table.
|
|
206
|
+
objectId: actorId.href,
|
|
207
|
+
};
|
|
208
|
+
admin.putOutboundActivity({
|
|
209
|
+
...about,
|
|
210
|
+
slug: null,
|
|
211
|
+
json: JSON.stringify(await activity.toJsonLd({ format: 'compact', contextLoader: context.contextLoader })),
|
|
212
|
+
});
|
|
213
|
+
return await fanOut(context, activity, about);
|
|
214
|
+
}).catch((thrown) => {
|
|
215
|
+
// A profile update is a side effect of saving the settings, and losing
|
|
216
|
+
// it must not lose the save.
|
|
217
|
+
logger.warn(`The actor update could not be delivered: ${messageOf(thrown)}`);
|
|
218
|
+
return undefined;
|
|
219
|
+
});
|
|
220
|
+
},
|
|
221
|
+
async redeliver(activityId) {
|
|
222
|
+
const stored = admin.getOutboundActivity(activityId);
|
|
223
|
+
if (stored === undefined)
|
|
224
|
+
return undefined;
|
|
225
|
+
return await enqueue(async () => {
|
|
226
|
+
const context = deliveryContext();
|
|
227
|
+
const activity = await Activity.fromJsonLd(JSON.parse(stored.json), {
|
|
228
|
+
contextLoader: context.contextLoader,
|
|
229
|
+
documentLoader: context.documentLoader,
|
|
230
|
+
});
|
|
231
|
+
return await fanOut(context, activity, {
|
|
232
|
+
activityId: stored.activityId,
|
|
233
|
+
activityType: stored.activityType,
|
|
234
|
+
objectId: stored.objectId,
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
},
|
|
238
|
+
settled() {
|
|
239
|
+
return chain.then(ignore);
|
|
240
|
+
},
|
|
241
|
+
};
|
|
242
|
+
/**
|
|
243
|
+
* Put one delivery on the queue and stop caring about its result.
|
|
244
|
+
*
|
|
245
|
+
* Nothing is waiting for a delivery that a change set off, so a rejection
|
|
246
|
+
* here has nowhere to go but the log; the per-follower failures are already
|
|
247
|
+
* in the database by the time one gets this far.
|
|
248
|
+
*/
|
|
249
|
+
function queue(task) {
|
|
250
|
+
enqueue(task).catch((thrown) => {
|
|
251
|
+
logger.warn(`A delivery failed: ${messageOf(thrown)}`);
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Followers keyed by the inbox one POST to which reaches them: the instance's
|
|
257
|
+
* shared inbox where it published one, and the follower's own where it did not.
|
|
258
|
+
*/
|
|
259
|
+
export function groupByInbox(followers) {
|
|
260
|
+
const groups = new Map();
|
|
261
|
+
for (const follower of followers) {
|
|
262
|
+
const inbox = follower.sharedInboxId ?? follower.inboxId;
|
|
263
|
+
const group = groups.get(inbox);
|
|
264
|
+
if (group === undefined)
|
|
265
|
+
groups.set(inbox, [follower]);
|
|
266
|
+
else
|
|
267
|
+
group.push(follower);
|
|
268
|
+
}
|
|
269
|
+
return groups;
|
|
270
|
+
}
|
|
271
|
+
/** The document, when it is one this site federates, and `undefined` otherwise. */
|
|
272
|
+
function federatedOrUndefined(document) {
|
|
273
|
+
if (document === undefined)
|
|
274
|
+
return undefined;
|
|
275
|
+
return isFederatedDocument(document) ? document : undefined;
|
|
276
|
+
}
|
|
277
|
+
/** An activity's short type name — `Create`, `Update`, `Delete` — from its type URI. */
|
|
278
|
+
function typeNameOf(activity) {
|
|
279
|
+
const typeId = getTypeId(activity);
|
|
280
|
+
return typeId.hash === '' ? typeId.href : typeId.hash.slice(1);
|
|
281
|
+
}
|
|
282
|
+
function messageOf(error) {
|
|
283
|
+
return error instanceof Error ? error.message : String(error);
|
|
284
|
+
}
|
|
285
|
+
function ignore() {
|
|
286
|
+
// Deliberately empty: the chain must not reject, and a failure has already
|
|
287
|
+
// been recorded against the followers it happened to.
|
|
288
|
+
}
|
|
289
|
+
//# sourceMappingURL=delivery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delivery.js","sourceRoot":"","sources":["../../src/federation/delivery.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { Federation, FederationOptions } from '@fedify/fedify';
|
|
2
|
+
import type { AdminStore } from '../admin/store.ts';
|
|
3
|
+
import type { FederationOverrides, ResolvedConfig } from '../config.ts';
|
|
4
|
+
import type { Document } from '../content/document.ts';
|
|
5
|
+
import type { ContentStore } from '../content/store.ts';
|
|
6
|
+
/** The `software.name` this CMS reports in NodeInfo. Lower case, as the schema demands. */
|
|
7
|
+
export declare const SOFTWARE_NAME = "geekity-cms";
|
|
8
|
+
/**
|
|
9
|
+
* How many activities one page of the outbox holds.
|
|
10
|
+
*
|
|
11
|
+
* A constant rather than the site's `postsPerPage`, because an outbox cursor
|
|
12
|
+
* is a URL a peer may come back to: changing the setting would silently move
|
|
13
|
+
* every page boundary underneath whoever is walking the collection.
|
|
14
|
+
*/
|
|
15
|
+
export declare const OUTBOX_PAGE_SIZE = 20;
|
|
16
|
+
/** What every Fedify dispatcher in the CMS is handed. */
|
|
17
|
+
export interface FederationContextData {
|
|
18
|
+
/** Settings and, through them, the actor's profile and its keys. */
|
|
19
|
+
readonly admin: AdminStore;
|
|
20
|
+
/** The content index, which the outbox and the post objects are built from. */
|
|
21
|
+
readonly store: ContentStore;
|
|
22
|
+
/** Config after defaults and environment overrides. */
|
|
23
|
+
readonly config: ResolvedConfig;
|
|
24
|
+
}
|
|
25
|
+
/** A CMS federation object, with the context data its dispatchers expect. */
|
|
26
|
+
export type SiteFederation = Federation<FederationContextData>;
|
|
27
|
+
/**
|
|
28
|
+
* What {@link createSiteFederation} takes: the site's base URL, and the
|
|
29
|
+
* {@link FederationOverrides} a site may swap the defaults for.
|
|
30
|
+
*/
|
|
31
|
+
export interface CreateSiteFederationOptions extends FederationOverrides {
|
|
32
|
+
/**
|
|
33
|
+
* The site's base URL. Fedify otherwise mints ids from `request.url`, which
|
|
34
|
+
* behind a proxy is the internal address rather than the one peers
|
|
35
|
+
* dereference, so this is the one option with no sensible default.
|
|
36
|
+
*
|
|
37
|
+
* Only its origin is used; see {@link federationOrigin}.
|
|
38
|
+
*/
|
|
39
|
+
baseUrl: string;
|
|
40
|
+
/** Anything else Fedify takes, for a site that needs to reach past this factory. */
|
|
41
|
+
federationOptions?: Partial<FederationOptions<FederationContextData>> | undefined;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Build the site's `Federation` object: the actor, its keys, the post objects,
|
|
45
|
+
* the outbox, WebFinger and NodeInfo.
|
|
46
|
+
*
|
|
47
|
+
* It is a factory rather than a module-level singleton so the KV store and the
|
|
48
|
+
* queue are arguments (decision-5), so a test can build one per data directory,
|
|
49
|
+
* and so a site that outgrows the in-memory pair changes a call rather than
|
|
50
|
+
* this file. The followers come out of SQLite; the following collection is
|
|
51
|
+
* empty, and doc-4 says it always will be.
|
|
52
|
+
*/
|
|
53
|
+
export declare function createSiteFederation(options: CreateSiteFederationOptions): SiteFederation;
|
|
54
|
+
/**
|
|
55
|
+
* The published post a slug names, or `undefined`.
|
|
56
|
+
*
|
|
57
|
+
* Slugs are not unique across years, and the index answers with the newest
|
|
58
|
+
* match. That is the same rule the admin's own slug lookup follows, so a
|
|
59
|
+
* collision resolves the same way wherever it is asked about.
|
|
60
|
+
*/
|
|
61
|
+
export declare function federatedPost(store: ContentStore, slug: string): Document | undefined;
|
|
62
|
+
/**
|
|
63
|
+
* The post whose ActivityStreams object is served at `/ap/posts/{slug}`.
|
|
64
|
+
*
|
|
65
|
+
* Nearly always the post with that slug. The exception is a post renamed after
|
|
66
|
+
* it was announced: it keeps the id its old slug minted, written into
|
|
67
|
+
* `activitypub.id` (doc-4), so the old URL has to keep answering or every
|
|
68
|
+
* follower's copy points at a 404. That case is found by searching the
|
|
69
|
+
* archive for the post holding the id, which is a walk rather than an index
|
|
70
|
+
* lookup — the front matter is the source of truth (decision-1) and the index
|
|
71
|
+
* has no column for it — but it is only reached when the slug itself does not
|
|
72
|
+
* answer.
|
|
73
|
+
*/
|
|
74
|
+
export declare function federatedObject(store: ContentStore, slug: string, baseUrl: string): Document | undefined;
|
|
75
|
+
//# sourceMappingURL=federation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"federation.d.ts","sourceRoot":"","sources":["../../src/federation/federation.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,iBAAiB,EAA6B,MAAM,gBAAgB,CAAC;AAI/F,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,KAAK,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACxE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAmBxD,2FAA2F;AAC3F,eAAO,MAAM,aAAa,gBAAgB,CAAC;AAE3C;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,KAAK,CAAC;AAEnC,yDAAyD;AACzD,MAAM,WAAW,qBAAqB;IACpC,oEAAoE;IACpE,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAC3B,+EAA+E;IAC/E,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;IAC7B,uDAAuD;IACvD,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;CACjC;AAED,6EAA6E;AAC7E,MAAM,MAAM,cAAc,GAAG,UAAU,CAAC,qBAAqB,CAAC,CAAC;AAE/D;;;GAGG;AACH,MAAM,WAAW,2BAA4B,SAAQ,mBAAmB;IACtE;;;;;;OAMG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB,oFAAoF;IACpF,iBAAiB,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,qBAAqB,CAAC,CAAC,GAAG,SAAS,CAAC;CACnF;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,2BAA2B,GAAG,cAAc,CAoHzF;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAGrF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,YAAY,EACnB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,GACd,QAAQ,GAAG,SAAS,CAUtB"}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
import { createFederation, InProcessMessageQueue, MemoryKvStore } from '@fedify/fedify';
|
|
3
|
+
import { Announce, Article, Create, Delete, Follow, Like, Undo } from '@fedify/vocab';
|
|
4
|
+
import { readSiteSettings } from '../admin/settings.js';
|
|
5
|
+
import { siteActor } from './actor.js';
|
|
6
|
+
import { isFederatedDocument, postArticle, postCreateActivity } from './article.js';
|
|
7
|
+
import { followersPage, lastFollowersCursor } from './followers.js';
|
|
8
|
+
import { handleDelete, handleFollow, handleLoggedActivity, handleUndo } from './inbox.js';
|
|
9
|
+
import { loadActorKeyPairs, SITE_ACTOR_IDENTIFIER } from './keys.js';
|
|
10
|
+
import { ACTOR_PATH, federationOrigin, FOLLOWERS_PATH, FOLLOWING_PATH, INBOX_PATH, NODEINFO_PATH, OUTBOX_PATH, POST_OBJECT_PATH, postObjectId, SHARED_INBOX_PATH, } from './paths.js';
|
|
11
|
+
/** The `software.name` this CMS reports in NodeInfo. Lower case, as the schema demands. */
|
|
12
|
+
export const SOFTWARE_NAME = 'geekity-cms';
|
|
13
|
+
/**
|
|
14
|
+
* How many activities one page of the outbox holds.
|
|
15
|
+
*
|
|
16
|
+
* A constant rather than the site's `postsPerPage`, because an outbox cursor
|
|
17
|
+
* is a URL a peer may come back to: changing the setting would silently move
|
|
18
|
+
* every page boundary underneath whoever is walking the collection.
|
|
19
|
+
*/
|
|
20
|
+
export const OUTBOX_PAGE_SIZE = 20;
|
|
21
|
+
/**
|
|
22
|
+
* Build the site's `Federation` object: the actor, its keys, the post objects,
|
|
23
|
+
* the outbox, WebFinger and NodeInfo.
|
|
24
|
+
*
|
|
25
|
+
* It is a factory rather than a module-level singleton so the KV store and the
|
|
26
|
+
* queue are arguments (decision-5), so a test can build one per data directory,
|
|
27
|
+
* and so a site that outgrows the in-memory pair changes a call rather than
|
|
28
|
+
* this file. The followers come out of SQLite; the following collection is
|
|
29
|
+
* empty, and doc-4 says it always will be.
|
|
30
|
+
*/
|
|
31
|
+
export function createSiteFederation(options) {
|
|
32
|
+
const federation = createFederation({
|
|
33
|
+
kv: options.kv ?? new MemoryKvStore(),
|
|
34
|
+
origin: federationOrigin(options.baseUrl),
|
|
35
|
+
// `null` is not the same as absent: it asks for no queue at all, so an
|
|
36
|
+
// activity is handled and delivered inside the request that carried it.
|
|
37
|
+
// Leaving the key off entirely is how Fedify is told that, which is why
|
|
38
|
+
// this is a spread rather than a value.
|
|
39
|
+
...(options.queue === null ? {} : { queue: options.queue ?? new InProcessMessageQueue() }),
|
|
40
|
+
...(options.allowPrivateAddress === undefined
|
|
41
|
+
? {}
|
|
42
|
+
: { allowPrivateAddress: options.allowPrivateAddress }),
|
|
43
|
+
...options.federationOptions,
|
|
44
|
+
});
|
|
45
|
+
federation
|
|
46
|
+
.setActorDispatcher(ACTOR_PATH, async (context, identifier) => {
|
|
47
|
+
if (identifier !== SITE_ACTOR_IDENTIFIER)
|
|
48
|
+
return null;
|
|
49
|
+
const settings = readSiteSettings(context.data.admin);
|
|
50
|
+
return await siteActor(context, identifier, {
|
|
51
|
+
settings,
|
|
52
|
+
baseUrl: context.data.config.baseUrl,
|
|
53
|
+
});
|
|
54
|
+
})
|
|
55
|
+
// WebFinger asks for a username; this is what turns the handle somebody
|
|
56
|
+
// typed into the identifier the URLs are built from, and what makes the
|
|
57
|
+
// handle a setting rather than part of the actor's id.
|
|
58
|
+
.mapHandle((context, username) => username === readSiteSettings(context.data.admin).actorHandle ? SITE_ACTOR_IDENTIFIER : null)
|
|
59
|
+
.setKeyPairsDispatcher(async (context, identifier) => identifier === SITE_ACTOR_IDENTIFIER
|
|
60
|
+
? await loadActorKeyPairs(context.data.admin, identifier)
|
|
61
|
+
: []);
|
|
62
|
+
// A post's ActivityStreams object. `null` is a 404, which is what a draft, a
|
|
63
|
+
// trashed post, a page and a slug that names nothing all get: the object
|
|
64
|
+
// dispatcher is the only thing that decides whether an object exists, so it
|
|
65
|
+
// cannot disagree with the outbox about it.
|
|
66
|
+
federation.setObjectDispatcher(Article, POST_OBJECT_PATH, (context, values) => {
|
|
67
|
+
const document = federatedObject(context.data.store, values.slug, context.data.config.baseUrl);
|
|
68
|
+
return document === undefined ? null : postArticle(context, document);
|
|
69
|
+
});
|
|
70
|
+
federation
|
|
71
|
+
.setOutboxDispatcher(OUTBOX_PATH, (context, identifier, cursor) => identifier === SITE_ACTOR_IDENTIFIER ? outboxPage(context, cursor) : null)
|
|
72
|
+
.setCounter((context, identifier) => identifier === SITE_ACTOR_IDENTIFIER ? context.data.store.counts().posts : null)
|
|
73
|
+
// Paging is cursor-based, and the cursor is an offset, so the first page
|
|
74
|
+
// is always the start of the archive and the last is wherever the archive
|
|
75
|
+
// currently ends.
|
|
76
|
+
.setFirstCursor((_context, identifier) => (identifier === SITE_ACTOR_IDENTIFIER ? '0' : null))
|
|
77
|
+
.setLastCursor((context, identifier) => {
|
|
78
|
+
if (identifier !== SITE_ACTOR_IDENTIFIER)
|
|
79
|
+
return null;
|
|
80
|
+
const total = context.data.store.counts().posts;
|
|
81
|
+
return String(total === 0 ? 0 : Math.floor((total - 1) / OUTBOX_PAGE_SIZE) * OUTBOX_PAGE_SIZE);
|
|
82
|
+
});
|
|
83
|
+
// The followers, straight out of SQLite and paged like the outbox. Fedify
|
|
84
|
+
// renders each one as its actor id, and hands the same rows — inbox and
|
|
85
|
+
// shared inbox included — to `ctx.sendActivity(…, 'followers', …)`, so this
|
|
86
|
+
// one dispatcher is both what a peer reads and where delivery fans out to.
|
|
87
|
+
federation
|
|
88
|
+
.setFollowersDispatcher(FOLLOWERS_PATH, (context, identifier, cursor) => identifier === SITE_ACTOR_IDENTIFIER ? followersPage(context, cursor) : null)
|
|
89
|
+
.setCounter((context, identifier) => identifier === SITE_ACTOR_IDENTIFIER ? context.data.admin.countFollowers() : null)
|
|
90
|
+
.setFirstCursor((_context, identifier) => (identifier === SITE_ACTOR_IDENTIFIER ? '0' : null))
|
|
91
|
+
.setLastCursor((context, identifier) => identifier === SITE_ACTOR_IDENTIFIER
|
|
92
|
+
? lastFollowersCursor(context.data.admin.countFollowers())
|
|
93
|
+
: null);
|
|
94
|
+
// Always empty, and always will be: doc-4 says the site follows nobody.
|
|
95
|
+
federation.setFollowingDispatcher(FOLLOWING_PATH, (_context, identifier) => identifier === SITE_ACTOR_IDENTIFIER ? { items: [] } : null);
|
|
96
|
+
// The inbox, personal and shared. Fedify has already verified the signature
|
|
97
|
+
// by the time a listener runs — an unsigned or badly signed delivery never
|
|
98
|
+
// reaches one — so a handler may trust that the activity's actor really sent
|
|
99
|
+
// it. An activity of a type not listed here is answered 202 and dropped,
|
|
100
|
+
// which is what doc-4 asks for everything past these five.
|
|
101
|
+
federation
|
|
102
|
+
.setInboxListeners(INBOX_PATH, SHARED_INBOX_PATH)
|
|
103
|
+
.on(Follow, handleFollow)
|
|
104
|
+
.on(Undo, handleUndo)
|
|
105
|
+
.on(Delete, handleDelete)
|
|
106
|
+
.on(Like, handleLoggedActivity)
|
|
107
|
+
.on(Announce, handleLoggedActivity)
|
|
108
|
+
.on(Create, handleLoggedActivity);
|
|
109
|
+
federation.setNodeInfoDispatcher(NODEINFO_PATH, (context) => {
|
|
110
|
+
const counts = context.data.store.counts();
|
|
111
|
+
return {
|
|
112
|
+
software: { name: SOFTWARE_NAME, version: packageVersion() },
|
|
113
|
+
protocols: ['activitypub'],
|
|
114
|
+
// A CMS is not a signup service; the admin makes accounts by hand.
|
|
115
|
+
openRegistrations: false,
|
|
116
|
+
usage: {
|
|
117
|
+
users: { total: context.data.admin.countUsers() },
|
|
118
|
+
localPosts: counts.posts,
|
|
119
|
+
localComments: 0,
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
});
|
|
123
|
+
return federation;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* The published post a slug names, or `undefined`.
|
|
127
|
+
*
|
|
128
|
+
* Slugs are not unique across years, and the index answers with the newest
|
|
129
|
+
* match. That is the same rule the admin's own slug lookup follows, so a
|
|
130
|
+
* collision resolves the same way wherever it is asked about.
|
|
131
|
+
*/
|
|
132
|
+
export function federatedPost(store, slug) {
|
|
133
|
+
const document = store.getBySlug(slug);
|
|
134
|
+
return document !== undefined && isFederatedDocument(document) ? document : undefined;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* The post whose ActivityStreams object is served at `/ap/posts/{slug}`.
|
|
138
|
+
*
|
|
139
|
+
* Nearly always the post with that slug. The exception is a post renamed after
|
|
140
|
+
* it was announced: it keeps the id its old slug minted, written into
|
|
141
|
+
* `activitypub.id` (doc-4), so the old URL has to keep answering or every
|
|
142
|
+
* follower's copy points at a 404. That case is found by searching the
|
|
143
|
+
* archive for the post holding the id, which is a walk rather than an index
|
|
144
|
+
* lookup — the front matter is the source of truth (decision-1) and the index
|
|
145
|
+
* has no column for it — but it is only reached when the slug itself does not
|
|
146
|
+
* answer.
|
|
147
|
+
*/
|
|
148
|
+
export function federatedObject(store, slug, baseUrl) {
|
|
149
|
+
const objectId = postObjectId(slug, baseUrl);
|
|
150
|
+
const direct = federatedPost(store, slug);
|
|
151
|
+
if (direct !== undefined) {
|
|
152
|
+
const stored = direct.activitypub?.id;
|
|
153
|
+
if (stored === undefined || stored === objectId)
|
|
154
|
+
return direct;
|
|
155
|
+
}
|
|
156
|
+
return store.listPosts().find((post) => post.activitypub?.id === objectId);
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* One page of the outbox: the `Create` that announced each post, newest first.
|
|
160
|
+
*
|
|
161
|
+
* The cursor is the offset into the archive as a decimal string. That keeps a
|
|
162
|
+
* page URL meaning what it meant when it was minted, which a cursor derived
|
|
163
|
+
* from the site's page size would not: the size is a setting somebody may
|
|
164
|
+
* change between two requests.
|
|
165
|
+
*/
|
|
166
|
+
function outboxPage(context, cursor) {
|
|
167
|
+
const offset = cursorOffset(cursor);
|
|
168
|
+
const { store } = context.data;
|
|
169
|
+
const documents = store.listPosts({ limit: OUTBOX_PAGE_SIZE, offset });
|
|
170
|
+
const total = store.counts().posts;
|
|
171
|
+
const next = offset + OUTBOX_PAGE_SIZE;
|
|
172
|
+
return {
|
|
173
|
+
items: documents.map((document) => postCreateActivity(context, document)),
|
|
174
|
+
nextCursor: next < total ? String(next) : null,
|
|
175
|
+
prevCursor: offset <= 0 ? null : String(Math.max(0, offset - OUTBOX_PAGE_SIZE)),
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
/** A cursor as an offset. Anything unreadable starts at the beginning. */
|
|
179
|
+
function cursorOffset(cursor) {
|
|
180
|
+
if (cursor === null || !/^[0-9]+$/.test(cursor))
|
|
181
|
+
return 0;
|
|
182
|
+
return Number(cursor);
|
|
183
|
+
}
|
|
184
|
+
/** The published version of `@geekity/cms`, which NodeInfo reports. */
|
|
185
|
+
function packageVersion() {
|
|
186
|
+
const require = createRequire(import.meta.url);
|
|
187
|
+
const manifest = require('../../package.json');
|
|
188
|
+
return manifest.version ?? '0.0.0';
|
|
189
|
+
}
|
|
190
|
+
//# sourceMappingURL=federation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"federation.js","sourceRoot":"","sources":["../../src/federation/federation.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Context, PageItems } from '@fedify/fedify';
|
|
2
|
+
import type { Recipient } from '@fedify/vocab';
|
|
3
|
+
import type { Follower } from '../admin/store.ts';
|
|
4
|
+
import type { FederationContextData } from './federation.ts';
|
|
5
|
+
/**
|
|
6
|
+
* How many followers one page of the collection holds.
|
|
7
|
+
*
|
|
8
|
+
* A constant for the same reason the outbox's is: a page URL is something a
|
|
9
|
+
* peer may come back to, and deriving the size from a setting would move every
|
|
10
|
+
* page boundary the moment somebody changed it.
|
|
11
|
+
*/
|
|
12
|
+
export declare const FOLLOWERS_PAGE_SIZE = 20;
|
|
13
|
+
/**
|
|
14
|
+
* A stored follower as the {@link Recipient} Fedify delivers to.
|
|
15
|
+
*
|
|
16
|
+
* The shared inbox is offered when the follower published one, because that is
|
|
17
|
+
* what lets a post addressed to a hundred followers on one instance become one
|
|
18
|
+
* POST instead of a hundred. Fedify renders a recipient in the collection as
|
|
19
|
+
* its `id` alone, so the inbox columns cost the collection nothing and are
|
|
20
|
+
* what `ctx.sendActivity(…, 'followers', …)` needs.
|
|
21
|
+
*/
|
|
22
|
+
export declare function followerRecipient(follower: Follower): Recipient;
|
|
23
|
+
/**
|
|
24
|
+
* The followers collection, newest follow first: one page of it, or all of it.
|
|
25
|
+
*
|
|
26
|
+
* A cursor is the offset into the list as a decimal string, exactly as the
|
|
27
|
+
* outbox's is, so the two collections page the same way and a cursor keeps
|
|
28
|
+
* meaning what it meant when it was minted.
|
|
29
|
+
*
|
|
30
|
+
* A `null` cursor is not the first page: it is Fedify asking for the whole
|
|
31
|
+
* collection at once, which is what it does before fanning an activity out to
|
|
32
|
+
* the followers. Answering it with a page would strand every follower past the
|
|
33
|
+
* first one silently, so this answers with all of them. The collection a peer
|
|
34
|
+
* reads never arrives here with `null`, because the first cursor is registered
|
|
35
|
+
* and a registered first cursor is what makes the collection paged.
|
|
36
|
+
*/
|
|
37
|
+
export declare function followersPage(context: Context<FederationContextData>, cursor: string | null): PageItems<Recipient>;
|
|
38
|
+
/**
|
|
39
|
+
* The cursor of the last page of the followers collection: the offset the
|
|
40
|
+
* final whole page starts at, and `0` for a site nobody follows yet.
|
|
41
|
+
*/
|
|
42
|
+
export declare function lastFollowersCursor(total: number): string;
|
|
43
|
+
//# sourceMappingURL=followers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"followers.d.ts","sourceRoot":"","sources":["../../src/federation/followers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE/C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAE7D;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,KAAK,CAAC;AAEtC;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,GAAG,SAAS,CAQ/D;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,OAAO,CAAC,qBAAqB,CAAC,EACvC,MAAM,EAAE,MAAM,GAAG,IAAI,GACpB,SAAS,CAAC,SAAS,CAAC,CAiBtB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAIzD"}
|