@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,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How many followers one page of the collection holds.
|
|
3
|
+
*
|
|
4
|
+
* A constant for the same reason the outbox's is: a page URL is something a
|
|
5
|
+
* peer may come back to, and deriving the size from a setting would move every
|
|
6
|
+
* page boundary the moment somebody changed it.
|
|
7
|
+
*/
|
|
8
|
+
export const FOLLOWERS_PAGE_SIZE = 20;
|
|
9
|
+
/**
|
|
10
|
+
* A stored follower as the {@link Recipient} Fedify delivers to.
|
|
11
|
+
*
|
|
12
|
+
* The shared inbox is offered when the follower published one, because that is
|
|
13
|
+
* what lets a post addressed to a hundred followers on one instance become one
|
|
14
|
+
* POST instead of a hundred. Fedify renders a recipient in the collection as
|
|
15
|
+
* its `id` alone, so the inbox columns cost the collection nothing and are
|
|
16
|
+
* what `ctx.sendActivity(…, 'followers', …)` needs.
|
|
17
|
+
*/
|
|
18
|
+
export function followerRecipient(follower) {
|
|
19
|
+
return {
|
|
20
|
+
id: new URL(follower.actorId),
|
|
21
|
+
inboxId: new URL(follower.inboxId),
|
|
22
|
+
endpoints: {
|
|
23
|
+
sharedInbox: follower.sharedInboxId === null ? null : new URL(follower.sharedInboxId),
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The followers collection, newest follow first: one page of it, or all of it.
|
|
29
|
+
*
|
|
30
|
+
* A cursor is the offset into the list as a decimal string, exactly as the
|
|
31
|
+
* outbox's is, so the two collections page the same way and a cursor keeps
|
|
32
|
+
* meaning what it meant when it was minted.
|
|
33
|
+
*
|
|
34
|
+
* A `null` cursor is not the first page: it is Fedify asking for the whole
|
|
35
|
+
* collection at once, which is what it does before fanning an activity out to
|
|
36
|
+
* the followers. Answering it with a page would strand every follower past the
|
|
37
|
+
* first one silently, so this answers with all of them. The collection a peer
|
|
38
|
+
* reads never arrives here with `null`, because the first cursor is registered
|
|
39
|
+
* and a registered first cursor is what makes the collection paged.
|
|
40
|
+
*/
|
|
41
|
+
export function followersPage(context, cursor) {
|
|
42
|
+
const { admin } = context.data;
|
|
43
|
+
if (cursor === null) {
|
|
44
|
+
return { items: admin.listFollowers().map(followerRecipient) };
|
|
45
|
+
}
|
|
46
|
+
const offset = cursorOffset(cursor);
|
|
47
|
+
const followers = admin.listFollowers({ limit: FOLLOWERS_PAGE_SIZE, offset });
|
|
48
|
+
const total = admin.countFollowers();
|
|
49
|
+
const next = offset + FOLLOWERS_PAGE_SIZE;
|
|
50
|
+
return {
|
|
51
|
+
items: followers.map(followerRecipient),
|
|
52
|
+
nextCursor: next < total ? String(next) : null,
|
|
53
|
+
prevCursor: offset <= 0 ? null : String(Math.max(0, offset - FOLLOWERS_PAGE_SIZE)),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The cursor of the last page of the followers collection: the offset the
|
|
58
|
+
* final whole page starts at, and `0` for a site nobody follows yet.
|
|
59
|
+
*/
|
|
60
|
+
export function lastFollowersCursor(total) {
|
|
61
|
+
return String(total === 0 ? 0 : Math.floor((total - 1) / FOLLOWERS_PAGE_SIZE) * FOLLOWERS_PAGE_SIZE);
|
|
62
|
+
}
|
|
63
|
+
/** A cursor as an offset. Anything unreadable starts at the beginning. */
|
|
64
|
+
function cursorOffset(cursor) {
|
|
65
|
+
if (cursor === null || !/^[0-9]+$/.test(cursor))
|
|
66
|
+
return 0;
|
|
67
|
+
return Number(cursor);
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=followers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"followers.js","sourceRoot":"","sources":["../../src/federation/followers.ts"],"names":[],"mappings":"AAMA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,EAAE,CAAC;AAEtC;;;;;;;;GAQG;AACH,MAAM,UAAU,iBAAiB,CAAC,QAAkB;IAClD,OAAO;QACL,EAAE,EAAE,IAAI,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC;QAC7B,OAAO,EAAE,IAAI,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC;QAClC,SAAS,EAAE;YACT,WAAW,EAAE,QAAQ,CAAC,aAAa,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,aAAa,CAAC;SACtF;KACF,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,aAAa,CAC3B,OAAuC,EACvC,MAAqB;IAErB,MAAM,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAE/B,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QACpB,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,aAAa,EAAE,CAAC,GAAG,CAAC,iBAAiB,CAAC,EAAE,CAAC;IACjE,CAAC;IAED,MAAM,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IACpC,MAAM,SAAS,GAAG,KAAK,CAAC,aAAa,CAAC,EAAE,KAAK,EAAE,mBAAmB,EAAE,MAAM,EAAE,CAAC,CAAC;IAC9E,MAAM,KAAK,GAAG,KAAK,CAAC,cAAc,EAAE,CAAC;IACrC,MAAM,IAAI,GAAG,MAAM,GAAG,mBAAmB,CAAC;IAE1C,OAAO;QACL,KAAK,EAAE,SAAS,CAAC,GAAG,CAAC,iBAAiB,CAAC;QACvC,UAAU,EAAE,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI;QAC9C,UAAU,EAAE,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,mBAAmB,CAAC,CAAC;KACnF,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAAa;IAC/C,OAAO,MAAM,CACX,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,mBAAmB,CAAC,GAAG,mBAAmB,CACtF,CAAC;AACJ,CAAC;AAED,0EAA0E;AAC1E,SAAS,YAAY,CAAC,MAAqB;IACzC,IAAI,MAAM,KAAK,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC;QAAE,OAAO,CAAC,CAAC;IAC1D,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC;AACxB,CAAC"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { InboxContext } from '@fedify/fedify';
|
|
2
|
+
import { Follow } from '@fedify/vocab';
|
|
3
|
+
import type { Activity, Actor, Announce, Create, Delete, Like, Undo } from '@fedify/vocab';
|
|
4
|
+
import type { NewFollower } from '../admin/store.ts';
|
|
5
|
+
import type { FederationContextData } from './federation.ts';
|
|
6
|
+
/** What an inbox handler is handed: a Fedify context over the CMS's stores. */
|
|
7
|
+
export type SiteInboxContext = InboxContext<FederationContextData>;
|
|
8
|
+
/**
|
|
9
|
+
* Handle a `Follow`: store the follower and reply `Accept`.
|
|
10
|
+
*
|
|
11
|
+
* A `Follow` of anything but the site actor is ignored rather than refused —
|
|
12
|
+
* a peer that asked to follow a post has not done anything wrong, it has
|
|
13
|
+
* merely addressed something that does not accept followers. The reply goes
|
|
14
|
+
* to the follower's own inbox rather than through the followers collection,
|
|
15
|
+
* because at this moment the follow is not yet a fact for anybody but us.
|
|
16
|
+
*
|
|
17
|
+
* Following twice is a no-op beyond refreshing the stored profile: the store
|
|
18
|
+
* is keyed by actor id and keeps the original follow time, so a redelivered
|
|
19
|
+
* or repeated `Follow` cannot turn one follower into two.
|
|
20
|
+
*/
|
|
21
|
+
export declare function handleFollow(context: SiteInboxContext, follow: Follow): Promise<void>;
|
|
22
|
+
/**
|
|
23
|
+
* Handle an `Undo`: an undone `Follow` removes the follower.
|
|
24
|
+
*
|
|
25
|
+
* Only the actor that sent the `Follow` may undo it. Without that check any
|
|
26
|
+
* signed actor could unfollow the site on somebody else's behalf, since the
|
|
27
|
+
* activity carrying the instruction is signed by its sender and says nothing
|
|
28
|
+
* about who the follow belonged to.
|
|
29
|
+
*/
|
|
30
|
+
export declare function handleUndo(context: SiteInboxContext, undo: Undo): Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Handle a `Delete`: an actor deleting itself stops being a follower.
|
|
33
|
+
*
|
|
34
|
+
* The only `Delete` acted on is the one whose object is its own actor, which
|
|
35
|
+
* is how an instance announces that an account is gone. A `Delete` of a note
|
|
36
|
+
* or an article is logged and left alone; the site holds no copy of a remote
|
|
37
|
+
* object to remove.
|
|
38
|
+
*/
|
|
39
|
+
export declare function handleDelete(context: SiteInboxContext, activity: Delete): Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Handle a `Like`, an `Announce` or a `Create`: record it and do nothing else.
|
|
42
|
+
*
|
|
43
|
+
* doc-4 keeps these so a later phase can surface likes, boosts and comments.
|
|
44
|
+
* Nothing about them is interpreted here, because what that phase will want
|
|
45
|
+
* out of an activity is not knowable yet — which is also why the row keeps the
|
|
46
|
+
* whole JSON-LD rather than a few columns of it.
|
|
47
|
+
*/
|
|
48
|
+
export declare function handleLoggedActivity(context: SiteInboxContext, activity: Like | Announce | Create): Promise<void>;
|
|
49
|
+
/**
|
|
50
|
+
* Write one inbound activity to the log.
|
|
51
|
+
*
|
|
52
|
+
* Every handled activity goes through here, the follow traffic included, so
|
|
53
|
+
* the log is a complete record of what the inbox was told rather than of what
|
|
54
|
+
* the inbox ignored. An activity with no actor is dropped: Fedify has already
|
|
55
|
+
* refused anything whose signature does not match its actor, so an activity
|
|
56
|
+
* without one is malformed rather than anonymous.
|
|
57
|
+
*/
|
|
58
|
+
export declare function logActivity(context: SiteInboxContext, activity: Activity): Promise<void>;
|
|
59
|
+
/**
|
|
60
|
+
* A remote actor as the follower row that describes it, or `undefined` when
|
|
61
|
+
* it is not deliverable.
|
|
62
|
+
*
|
|
63
|
+
* An actor with no id or no inbox is dropped rather than stored: a follower
|
|
64
|
+
* the site could never deliver to is a row that would only ever fail, and
|
|
65
|
+
* both are the minimum ActivityPub asks of an actor.
|
|
66
|
+
*/
|
|
67
|
+
export declare function followerFrom(context: SiteInboxContext, actor: Actor): Promise<NewFollower | undefined>;
|
|
68
|
+
//# sourceMappingURL=inbox.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inbox.d.ts","sourceRoot":"","sources":["../../src/federation/inbox.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAU,MAAM,EAAa,MAAM,eAAe,CAAC;AAC1D,OAAO,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAQ,IAAI,EAAE,MAAM,eAAe,CAAC;AAEjG,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAG7D,+EAA+E;AAC/E,MAAM,MAAM,gBAAgB,GAAG,YAAY,CAAC,qBAAqB,CAAC,CAAC;AAEnE;;;;;;;;;;;;GAYG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA4B3F;AAED;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAAC,OAAO,EAAE,gBAAgB,EAAE,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAWrF;AAED;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAQ7F;AAED;;;;;;;GAOG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,gBAAgB,EACzB,QAAQ,EAAE,IAAI,GAAG,QAAQ,GAAG,MAAM,GACjC,OAAO,CAAC,IAAI,CAAC,CAEf;AAED;;;;;;;;GAQG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAgB9F;AAED;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAChC,OAAO,EAAE,gBAAgB,EACzB,KAAK,EAAE,KAAK,GACX,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CAclC"}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { Accept, Follow, getTypeId } from '@fedify/vocab';
|
|
3
|
+
import { SITE_ACTOR_IDENTIFIER } from './keys.js';
|
|
4
|
+
/**
|
|
5
|
+
* Handle a `Follow`: store the follower and reply `Accept`.
|
|
6
|
+
*
|
|
7
|
+
* A `Follow` of anything but the site actor is ignored rather than refused —
|
|
8
|
+
* a peer that asked to follow a post has not done anything wrong, it has
|
|
9
|
+
* merely addressed something that does not accept followers. The reply goes
|
|
10
|
+
* to the follower's own inbox rather than through the followers collection,
|
|
11
|
+
* because at this moment the follow is not yet a fact for anybody but us.
|
|
12
|
+
*
|
|
13
|
+
* Following twice is a no-op beyond refreshing the stored profile: the store
|
|
14
|
+
* is keyed by actor id and keeps the original follow time, so a redelivered
|
|
15
|
+
* or repeated `Follow` cannot turn one follower into two.
|
|
16
|
+
*/
|
|
17
|
+
export async function handleFollow(context, follow) {
|
|
18
|
+
await logActivity(context, follow);
|
|
19
|
+
if (follow.id === null || follow.objectId === null)
|
|
20
|
+
return;
|
|
21
|
+
// `parseUri` is what decides whether the object is this actor: comparing
|
|
22
|
+
// strings would have to know how Fedify spells the actor's URL, and this
|
|
23
|
+
// asks Fedify instead.
|
|
24
|
+
const parsed = context.parseUri(follow.objectId);
|
|
25
|
+
if (parsed?.type !== 'actor' || parsed.identifier !== SITE_ACTOR_IDENTIFIER)
|
|
26
|
+
return;
|
|
27
|
+
const actor = await follow.getActor(dereference(context));
|
|
28
|
+
if (actor === null)
|
|
29
|
+
return;
|
|
30
|
+
const follower = await followerFrom(context, actor);
|
|
31
|
+
if (follower === undefined)
|
|
32
|
+
return;
|
|
33
|
+
context.data.admin.putFollower(follower);
|
|
34
|
+
await context.sendActivity({ identifier: SITE_ACTOR_IDENTIFIER }, actor, new Accept({
|
|
35
|
+
// A fresh id every time: an actor may follow, unfollow and follow again,
|
|
36
|
+
// and those are three activities rather than one repeated.
|
|
37
|
+
id: new URL(`#accept/${randomUUID()}`, context.getActorUri(SITE_ACTOR_IDENTIFIER)),
|
|
38
|
+
actor: follow.objectId,
|
|
39
|
+
object: follow,
|
|
40
|
+
}));
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Handle an `Undo`: an undone `Follow` removes the follower.
|
|
44
|
+
*
|
|
45
|
+
* Only the actor that sent the `Follow` may undo it. Without that check any
|
|
46
|
+
* signed actor could unfollow the site on somebody else's behalf, since the
|
|
47
|
+
* activity carrying the instruction is signed by its sender and says nothing
|
|
48
|
+
* about who the follow belonged to.
|
|
49
|
+
*/
|
|
50
|
+
export async function handleUndo(context, undo) {
|
|
51
|
+
await logActivity(context, undo);
|
|
52
|
+
const object = await undo.getObject(dereference(context));
|
|
53
|
+
if (!(object instanceof Follow))
|
|
54
|
+
return;
|
|
55
|
+
const undoer = undo.actorId;
|
|
56
|
+
const follower = object.actorId;
|
|
57
|
+
if (undoer === null || follower === null || undoer.href !== follower.href)
|
|
58
|
+
return;
|
|
59
|
+
context.data.admin.deleteFollower(follower.href);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Handle a `Delete`: an actor deleting itself stops being a follower.
|
|
63
|
+
*
|
|
64
|
+
* The only `Delete` acted on is the one whose object is its own actor, which
|
|
65
|
+
* is how an instance announces that an account is gone. A `Delete` of a note
|
|
66
|
+
* or an article is logged and left alone; the site holds no copy of a remote
|
|
67
|
+
* object to remove.
|
|
68
|
+
*/
|
|
69
|
+
export async function handleDelete(context, activity) {
|
|
70
|
+
await logActivity(context, activity);
|
|
71
|
+
const actor = activity.actorId;
|
|
72
|
+
const object = activity.objectId;
|
|
73
|
+
if (actor === null || object === null || actor.href !== object.href)
|
|
74
|
+
return;
|
|
75
|
+
context.data.admin.deleteFollower(object.href);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Handle a `Like`, an `Announce` or a `Create`: record it and do nothing else.
|
|
79
|
+
*
|
|
80
|
+
* doc-4 keeps these so a later phase can surface likes, boosts and comments.
|
|
81
|
+
* Nothing about them is interpreted here, because what that phase will want
|
|
82
|
+
* out of an activity is not knowable yet — which is also why the row keeps the
|
|
83
|
+
* whole JSON-LD rather than a few columns of it.
|
|
84
|
+
*/
|
|
85
|
+
export async function handleLoggedActivity(context, activity) {
|
|
86
|
+
await logActivity(context, activity);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Write one inbound activity to the log.
|
|
90
|
+
*
|
|
91
|
+
* Every handled activity goes through here, the follow traffic included, so
|
|
92
|
+
* the log is a complete record of what the inbox was told rather than of what
|
|
93
|
+
* the inbox ignored. An activity with no actor is dropped: Fedify has already
|
|
94
|
+
* refused anything whose signature does not match its actor, so an activity
|
|
95
|
+
* without one is malformed rather than anonymous.
|
|
96
|
+
*/
|
|
97
|
+
export async function logActivity(context, activity) {
|
|
98
|
+
const actorId = activity.actorId;
|
|
99
|
+
if (actorId === null)
|
|
100
|
+
return;
|
|
101
|
+
const json = await activity.toJsonLd({
|
|
102
|
+
format: 'compact',
|
|
103
|
+
contextLoader: context.contextLoader,
|
|
104
|
+
});
|
|
105
|
+
context.data.admin.logInboxActivity({
|
|
106
|
+
activityId: activity.id?.href ?? null,
|
|
107
|
+
activityType: activityTypeName(activity),
|
|
108
|
+
actorId: actorId.href,
|
|
109
|
+
objectId: activity.objectId?.href ?? null,
|
|
110
|
+
json: JSON.stringify(json),
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* A remote actor as the follower row that describes it, or `undefined` when
|
|
115
|
+
* it is not deliverable.
|
|
116
|
+
*
|
|
117
|
+
* An actor with no id or no inbox is dropped rather than stored: a follower
|
|
118
|
+
* the site could never deliver to is a row that would only ever fail, and
|
|
119
|
+
* both are the minimum ActivityPub asks of an actor.
|
|
120
|
+
*/
|
|
121
|
+
export async function followerFrom(context, actor) {
|
|
122
|
+
if (actor.id === null || actor.inboxId === null)
|
|
123
|
+
return undefined;
|
|
124
|
+
const icon = await actor.getIcon({ ...dereference(context), suppressError: true });
|
|
125
|
+
return {
|
|
126
|
+
actorId: actor.id.href,
|
|
127
|
+
inboxId: actor.inboxId.href,
|
|
128
|
+
sharedInboxId: actor.endpoints?.sharedInbox?.href ?? null,
|
|
129
|
+
handle: actorHandle(actor),
|
|
130
|
+
name: actor.name === null ? null : actor.name.toString(),
|
|
131
|
+
iconUrl: linkHref(icon?.url ?? null) ?? actor.iconId?.href ?? null,
|
|
132
|
+
url: linkHref(actor.url) ?? null,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* `@name@host` for an actor, built from what the actor document says rather
|
|
137
|
+
* than from WebFinger.
|
|
138
|
+
*
|
|
139
|
+
* A canonical handle would take a WebFinger round trip per follow to confirm
|
|
140
|
+
* the host, and this is display data: the id is what identifies the follower,
|
|
141
|
+
* and the id is what every other part of the system keys on.
|
|
142
|
+
*/
|
|
143
|
+
function actorHandle(actor) {
|
|
144
|
+
const username = actor.preferredUsername;
|
|
145
|
+
if (username === null || actor.id === null)
|
|
146
|
+
return null;
|
|
147
|
+
return `@${username.toString()}@${actor.id.host}`;
|
|
148
|
+
}
|
|
149
|
+
/** A `URL` or a `Link` as a plain href, or `null`. */
|
|
150
|
+
function linkHref(value) {
|
|
151
|
+
if (value === null)
|
|
152
|
+
return null;
|
|
153
|
+
return value instanceof URL ? value.href : (value.href?.href ?? null);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* The short ActivityStreams type name — `Follow`, `Like`, `Announce` — that the
|
|
157
|
+
* log stores.
|
|
158
|
+
*
|
|
159
|
+
* Taken from the type IRI rather than from the class name, which a bundler is
|
|
160
|
+
* free to rename, or from the JSON, which spells a type outside the core
|
|
161
|
+
* vocabulary as a full IRI.
|
|
162
|
+
*/
|
|
163
|
+
function activityTypeName(activity) {
|
|
164
|
+
const typeId = getTypeId(activity);
|
|
165
|
+
return typeId.hash === '' ? typeId.href : typeId.hash.slice(1);
|
|
166
|
+
}
|
|
167
|
+
/** The loaders a vocabulary getter needs to follow a link off this context. */
|
|
168
|
+
function dereference(context) {
|
|
169
|
+
return { documentLoader: context.documentLoader, contextLoader: context.contextLoader };
|
|
170
|
+
}
|
|
171
|
+
//# sourceMappingURL=inbox.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inbox.js","sourceRoot":"","sources":["../../src/federation/inbox.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAGzC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAK1D,OAAO,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AAKlD;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,OAAyB,EAAE,MAAc;IAC1E,MAAM,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IACnC,IAAI,MAAM,CAAC,EAAE,KAAK,IAAI,IAAI,MAAM,CAAC,QAAQ,KAAK,IAAI;QAAE,OAAO;IAE3D,yEAAyE;IACzE,yEAAyE;IACzE,uBAAuB;IACvB,MAAM,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IACjD,IAAI,MAAM,EAAE,IAAI,KAAK,OAAO,IAAI,MAAM,CAAC,UAAU,KAAK,qBAAqB;QAAE,OAAO;IAEpF,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC;IAC1D,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO;IAC3B,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IACpD,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO;IAEnC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;IAEzC,MAAM,OAAO,CAAC,YAAY,CACxB,EAAE,UAAU,EAAE,qBAAqB,EAAE,EACrC,KAAK,EACL,IAAI,MAAM,CAAC;QACT,yEAAyE;QACzE,2DAA2D;QAC3D,EAAE,EAAE,IAAI,GAAG,CAAC,WAAW,UAAU,EAAE,EAAE,EAAE,OAAO,CAAC,WAAW,CAAC,qBAAqB,CAAC,CAAC;QAClF,KAAK,EAAE,MAAM,CAAC,QAAQ;QACtB,MAAM,EAAE,MAAM;KACf,CAAC,CACH,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,OAAyB,EAAE,IAAU;IACpE,MAAM,WAAW,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IAEjC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC;IAC1D,IAAI,CAAC,CAAC,MAAM,YAAY,MAAM,CAAC;QAAE,OAAO;IAExC,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC;IAC5B,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC;IAChC,IAAI,MAAM,KAAK,IAAI,IAAI,QAAQ,KAAK,IAAI,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,CAAC,IAAI;QAAE,OAAO;IAElF,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;AACnD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,OAAyB,EAAE,QAAgB;IAC5E,MAAM,WAAW,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IAErC,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAC;IAC/B,MAAM,MAAM,GAAG,QAAQ,CAAC,QAAQ,CAAC;IACjC,IAAI,KAAK,KAAK,IAAI,IAAI,MAAM,KAAK,IAAI,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,CAAC,IAAI;QAAE,OAAO;IAE5E,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AACjD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,OAAyB,EACzB,QAAkC;IAElC,MAAM,WAAW,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;AACvC,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,OAAyB,EAAE,QAAkB;IAC7E,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC;IACjC,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO;IAE7B,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,QAAQ,CAAC;QACnC,MAAM,EAAE,SAAS;QACjB,aAAa,EAAE,OAAO,CAAC,aAAa;KACrC,CAAC,CAAC;IAEH,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,gBAAgB,CAAC;QAClC,UAAU,EAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,IAAI,IAAI;QACrC,YAAY,EAAE,gBAAgB,CAAC,QAAQ,CAAC;QACxC,OAAO,EAAE,OAAO,CAAC,IAAI;QACrB,QAAQ,EAAE,QAAQ,CAAC,QAAQ,EAAE,IAAI,IAAI,IAAI;QACzC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;KAC3B,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,OAAyB,EACzB,KAAY;IAEZ,IAAI,KAAK,CAAC,EAAE,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IAElE,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,EAAE,GAAG,WAAW,CAAC,OAAO,CAAC,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IAEnF,OAAO;QACL,OAAO,EAAE,KAAK,CAAC,EAAE,CAAC,IAAI;QACtB,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI;QAC3B,aAAa,EAAE,KAAK,CAAC,SAAS,EAAE,WAAW,EAAE,IAAI,IAAI,IAAI;QACzD,MAAM,EAAE,WAAW,CAAC,KAAK,CAAC;QAC1B,IAAI,EAAE,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE;QACxD,OAAO,EAAE,QAAQ,CAAC,IAAI,EAAE,GAAG,IAAI,IAAI,CAAC,IAAI,KAAK,CAAC,MAAM,EAAE,IAAI,IAAI,IAAI;QAClE,GAAG,EAAE,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,IAAI;KACjC,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,WAAW,CAAC,KAAY;IAC/B,MAAM,QAAQ,GAAG,KAAK,CAAC,iBAAiB,CAAC;IACzC,IAAI,QAAQ,KAAK,IAAI,IAAI,KAAK,CAAC,EAAE,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACxD,OAAO,IAAI,QAAQ,CAAC,QAAQ,EAAE,IAAI,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC;AACpD,CAAC;AAED,sDAAsD;AACtD,SAAS,QAAQ,CAAC,KAAwB;IACxC,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAChC,OAAO,KAAK,YAAY,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,IAAI,IAAI,CAAC,CAAC;AACxE,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,gBAAgB,CAAC,QAAkB;IAC1C,MAAM,MAAM,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;IACnC,OAAO,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACjE,CAAC;AAED,+EAA+E;AAC/E,SAAS,WAAW,CAAC,OAAyB;IAI5C,OAAO,EAAE,cAAc,EAAE,OAAO,CAAC,cAAc,EAAE,aAAa,EAAE,OAAO,CAAC,aAAa,EAAE,CAAC;AAC1F,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export { ACTOR_CLASSES, actorClassFor, avatarUrl, siteActor } from './actor.ts';
|
|
2
|
+
export type { SiteActorOptions } from './actor.ts';
|
|
3
|
+
export { articleObjectId, isFederatedDocument, postArticle, postCreateActivity, postDeleteActivity, postUpdateActivity, SOURCE_MEDIA_TYPE, toInstant, } from './article.ts';
|
|
4
|
+
export { createDeliveryService, groupByInbox } from './delivery.ts';
|
|
5
|
+
export type { CreateDeliveryServiceOptions, DeliveryLogger, DeliveryReport, DeliveryService, } from './delivery.ts';
|
|
6
|
+
export { createSiteFederation, federatedObject, federatedPost, OUTBOX_PAGE_SIZE, SOFTWARE_NAME, } from './federation.ts';
|
|
7
|
+
export type { CreateSiteFederationOptions, FederationContextData, SiteFederation, } from './federation.ts';
|
|
8
|
+
export { followerRecipient, followersPage, FOLLOWERS_PAGE_SIZE, lastFollowersCursor, } from './followers.ts';
|
|
9
|
+
export { followerFrom, handleDelete, handleFollow, handleLoggedActivity, handleUndo, logActivity, } from './inbox.ts';
|
|
10
|
+
export type { SiteInboxContext } from './inbox.ts';
|
|
11
|
+
export { loadActorKeyPairs, SITE_ACTOR_IDENTIFIER } from './keys.ts';
|
|
12
|
+
export { mountFederation } from './mount.ts';
|
|
13
|
+
export { ACTOR_PATH, createActivityId, deleteActivityId, FEDERATION_PREFIX, federationOrigin, FOLLOWERS_PATH, FOLLOWING_PATH, INBOX_PATH, NODEINFO_PATH, OUTBOX_PATH, POST_OBJECT_PATH, postObjectId, postObjectPath, SHARED_INBOX_PATH, updateActivityId, } from './paths.ts';
|
|
14
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/federation/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAChF,YAAY,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,EACL,eAAe,EACf,mBAAmB,EACnB,WAAW,EACX,kBAAkB,EAClB,kBAAkB,EAClB,kBAAkB,EAClB,iBAAiB,EACjB,SAAS,GACV,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,qBAAqB,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AACpE,YAAY,EACV,4BAA4B,EAC5B,cAAc,EACd,cAAc,EACd,eAAe,GAChB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,oBAAoB,EACpB,eAAe,EACf,aAAa,EACb,gBAAgB,EAChB,aAAa,GACd,MAAM,iBAAiB,CAAC;AACzB,YAAY,EACV,2BAA2B,EAC3B,qBAAqB,EACrB,cAAc,GACf,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,iBAAiB,EACjB,aAAa,EACb,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,oBAAoB,EACpB,UAAU,EACV,WAAW,GACZ,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AACrE,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EACL,UAAU,EACV,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,EAChB,cAAc,EACd,cAAc,EACd,UAAU,EACV,aAAa,EACb,WAAW,EACX,gBAAgB,EAChB,YAAY,EACZ,cAAc,EACd,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { ACTOR_CLASSES, actorClassFor, avatarUrl, siteActor } from './actor.js';
|
|
2
|
+
export { articleObjectId, isFederatedDocument, postArticle, postCreateActivity, postDeleteActivity, postUpdateActivity, SOURCE_MEDIA_TYPE, toInstant, } from './article.js';
|
|
3
|
+
export { createDeliveryService, groupByInbox } from './delivery.js';
|
|
4
|
+
export { createSiteFederation, federatedObject, federatedPost, OUTBOX_PAGE_SIZE, SOFTWARE_NAME, } from './federation.js';
|
|
5
|
+
export { followerRecipient, followersPage, FOLLOWERS_PAGE_SIZE, lastFollowersCursor, } from './followers.js';
|
|
6
|
+
export { followerFrom, handleDelete, handleFollow, handleLoggedActivity, handleUndo, logActivity, } from './inbox.js';
|
|
7
|
+
export { loadActorKeyPairs, SITE_ACTOR_IDENTIFIER } from './keys.js';
|
|
8
|
+
export { mountFederation } from './mount.js';
|
|
9
|
+
export { ACTOR_PATH, createActivityId, deleteActivityId, FEDERATION_PREFIX, federationOrigin, FOLLOWERS_PATH, FOLLOWING_PATH, INBOX_PATH, NODEINFO_PATH, OUTBOX_PATH, POST_OBJECT_PATH, postObjectId, postObjectPath, SHARED_INBOX_PATH, updateActivityId, } from './paths.js';
|
|
10
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/federation/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEhF,OAAO,EACL,eAAe,EACf,mBAAmB,EACnB,WAAW,EACX,kBAAkB,EAClB,kBAAkB,EAClB,kBAAkB,EAClB,iBAAiB,EACjB,SAAS,GACV,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,qBAAqB,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAOpE,OAAO,EACL,oBAAoB,EACpB,eAAe,EACf,aAAa,EACb,gBAAgB,EAChB,aAAa,GACd,MAAM,iBAAiB,CAAC;AAMzB,OAAO,EACL,iBAAiB,EACjB,aAAa,EACb,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,oBAAoB,EACpB,UAAU,EACV,WAAW,GACZ,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AACrE,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EACL,UAAU,EACV,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,EAChB,cAAc,EACd,cAAc,EACd,UAAU,EACV,aAAa,EACb,WAAW,EACX,gBAAgB,EAChB,YAAY,EACZ,cAAc,EACd,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { AdminStore } from '../admin/store.ts';
|
|
2
|
+
/**
|
|
3
|
+
* The site actor's internal identifier.
|
|
4
|
+
*
|
|
5
|
+
* Fedify keys actors by an identifier rather than by handle, and the two are
|
|
6
|
+
* deliberately not the same thing here: the handle is a setting somebody may
|
|
7
|
+
* change, while the identifier is what the actor's id, its inbox, its outbox
|
|
8
|
+
* and its stored keys are all built from. Renaming `@blog@example.com` to
|
|
9
|
+
* `@notes@example.com` therefore leaves every URL — and every follower — alone.
|
|
10
|
+
*/
|
|
11
|
+
export declare const SITE_ACTOR_IDENTIFIER = "actor";
|
|
12
|
+
/**
|
|
13
|
+
* The actor's key pairs, generated on the first call and read from SQLite on
|
|
14
|
+
* every one after that.
|
|
15
|
+
*
|
|
16
|
+
* Both of {@link ACTOR_KEY_ALGORITHMS} are returned, in that order, which is
|
|
17
|
+
* the order Fedify wants them in: the RSA pair signs HTTP Signatures for
|
|
18
|
+
* Mastodon and friends, the Ed25519 pair signs FEP-8b32 integrity proofs.
|
|
19
|
+
*
|
|
20
|
+
* A stored pair that will not import — a half-written row, a key from a JWK
|
|
21
|
+
* shape that has since changed — is replaced rather than thrown, so a damaged
|
|
22
|
+
* table heals on the next boot instead of wedging every boot after it. That
|
|
23
|
+
* costs the site its old key for that algorithm, which is why the two are
|
|
24
|
+
* stored and healed independently.
|
|
25
|
+
*/
|
|
26
|
+
export declare function loadActorKeyPairs(store: AdminStore, identifier: string): Promise<CryptoKeyPair[]>;
|
|
27
|
+
//# sourceMappingURL=keys.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../../src/federation/keys.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAqB,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAEvE;;;;;;;;GAQG;AACH,eAAO,MAAM,qBAAqB,UAAU,CAAC;AAE7C;;;;;;;;;;;;;GAaG;AACH,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,UAAU,EACjB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,aAAa,EAAE,CAAC,CAY1B"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { exportJwk, generateCryptoKeyPair, importJwk } from '@fedify/fedify';
|
|
2
|
+
import { ACTOR_KEY_ALGORITHMS } from '../admin/store.js';
|
|
3
|
+
/**
|
|
4
|
+
* The site actor's internal identifier.
|
|
5
|
+
*
|
|
6
|
+
* Fedify keys actors by an identifier rather than by handle, and the two are
|
|
7
|
+
* deliberately not the same thing here: the handle is a setting somebody may
|
|
8
|
+
* change, while the identifier is what the actor's id, its inbox, its outbox
|
|
9
|
+
* and its stored keys are all built from. Renaming `@blog@example.com` to
|
|
10
|
+
* `@notes@example.com` therefore leaves every URL — and every follower — alone.
|
|
11
|
+
*/
|
|
12
|
+
export const SITE_ACTOR_IDENTIFIER = 'actor';
|
|
13
|
+
/**
|
|
14
|
+
* The actor's key pairs, generated on the first call and read from SQLite on
|
|
15
|
+
* every one after that.
|
|
16
|
+
*
|
|
17
|
+
* Both of {@link ACTOR_KEY_ALGORITHMS} are returned, in that order, which is
|
|
18
|
+
* the order Fedify wants them in: the RSA pair signs HTTP Signatures for
|
|
19
|
+
* Mastodon and friends, the Ed25519 pair signs FEP-8b32 integrity proofs.
|
|
20
|
+
*
|
|
21
|
+
* A stored pair that will not import — a half-written row, a key from a JWK
|
|
22
|
+
* shape that has since changed — is replaced rather than thrown, so a damaged
|
|
23
|
+
* table heals on the next boot instead of wedging every boot after it. That
|
|
24
|
+
* costs the site its old key for that algorithm, which is why the two are
|
|
25
|
+
* stored and healed independently.
|
|
26
|
+
*/
|
|
27
|
+
export async function loadActorKeyPairs(store, identifier) {
|
|
28
|
+
const stored = new Map(store.listActorKeys(identifier).map((key) => [key.algorithm, key]));
|
|
29
|
+
const pairs = [];
|
|
30
|
+
for (const algorithm of ACTOR_KEY_ALGORITHMS) {
|
|
31
|
+
const row = stored.get(algorithm);
|
|
32
|
+
const imported = row === undefined ? undefined : await importPair(row.privateJwk, row.publicJwk);
|
|
33
|
+
pairs.push(imported ?? (await generatePair(store, identifier, algorithm)));
|
|
34
|
+
}
|
|
35
|
+
return pairs;
|
|
36
|
+
}
|
|
37
|
+
/** A stored pair as `CryptoKey`s, or `undefined` when it will not import. */
|
|
38
|
+
async function importPair(privateJwk, publicJwk) {
|
|
39
|
+
try {
|
|
40
|
+
return {
|
|
41
|
+
privateKey: await importJwk(JSON.parse(privateJwk), 'private'),
|
|
42
|
+
publicKey: await importJwk(JSON.parse(publicJwk), 'public'),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/** A fresh pair, written to the store before it is handed back. */
|
|
50
|
+
async function generatePair(store, identifier, algorithm) {
|
|
51
|
+
const pair = await generateCryptoKeyPair(algorithm);
|
|
52
|
+
store.putActorKey({
|
|
53
|
+
identifier,
|
|
54
|
+
algorithm,
|
|
55
|
+
privateJwk: JSON.stringify(await exportJwk(pair.privateKey)),
|
|
56
|
+
publicJwk: JSON.stringify(await exportJwk(pair.publicKey)),
|
|
57
|
+
});
|
|
58
|
+
return pair;
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=keys.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keys.js","sourceRoot":"","sources":["../../src/federation/keys.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,qBAAqB,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE7E,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAGzD;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,OAAO,CAAC;AAE7C;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,KAAiB,EACjB,UAAkB;IAElB,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;IAC3F,MAAM,KAAK,GAAoB,EAAE,CAAC;IAElC,KAAK,MAAM,SAAS,IAAI,oBAAoB,EAAE,CAAC;QAC7C,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAClC,MAAM,QAAQ,GACZ,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,UAAU,CAAC,GAAG,CAAC,UAAU,EAAE,GAAG,CAAC,SAAS,CAAC,CAAC;QAClF,KAAK,CAAC,IAAI,CAAC,QAAQ,IAAI,CAAC,MAAM,YAAY,CAAC,KAAK,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC;IAC7E,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,6EAA6E;AAC7E,KAAK,UAAU,UAAU,CACvB,UAAkB,EAClB,SAAiB;IAEjB,IAAI,CAAC;QACH,OAAO;YACL,UAAU,EAAE,MAAM,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAe,EAAE,SAAS,CAAC;YAC5E,SAAS,EAAE,MAAM,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,CAAe,EAAE,QAAQ,CAAC;SAC1E,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,mEAAmE;AACnE,KAAK,UAAU,YAAY,CACzB,KAAiB,EACjB,UAAkB,EAClB,SAA4B;IAE5B,MAAM,IAAI,GAAG,MAAM,qBAAqB,CAAC,SAAS,CAAC,CAAC;IACpD,KAAK,CAAC,WAAW,CAAC;QAChB,UAAU;QACV,SAAS;QACT,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,SAAS,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC5D,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;KAC3D,CAAC,CAAC;IACH,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Hono } from 'hono';
|
|
2
|
+
import type { GeekityEnv } from '../env.ts';
|
|
3
|
+
import type { SiteFederation } from './federation.ts';
|
|
4
|
+
/**
|
|
5
|
+
* Put Fedify in front of the rest of the app.
|
|
6
|
+
*
|
|
7
|
+
* The middleware only answers the paths Fedify registered — the actor, its
|
|
8
|
+
* collections, the post objects, WebFinger and NodeInfo; everything else falls
|
|
9
|
+
* through to `next()` and reaches the admin and the public site as before.
|
|
10
|
+
* That is why it is mounted first: the public site claims every unmatched path
|
|
11
|
+
* in its not-found handler, so anything registered after it never sees a
|
|
12
|
+
* request.
|
|
13
|
+
*
|
|
14
|
+
* A post permalink asked for as ActivityStreams is answered here too, by
|
|
15
|
+
* {@link activityStreamsDocument}, because Fedify only knows about the URLs it
|
|
16
|
+
* minted and a permalink is not one of them.
|
|
17
|
+
*
|
|
18
|
+
* The dispatchers are handed the stores off the Hono context rather than
|
|
19
|
+
* closing over them, so one federation object could serve more than one CMS
|
|
20
|
+
* and so a dispatcher is never reading a store the request did not come with.
|
|
21
|
+
*/
|
|
22
|
+
export declare function mountFederation(app: Hono<GeekityEnv>, federation: SiteFederation): void;
|
|
23
|
+
//# sourceMappingURL=mount.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mount.d.ts","sourceRoot":"","sources":["../../src/federation/mount.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAW,IAAI,EAAqB,MAAM,MAAM,CAAC;AAE7D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAK5C,OAAO,KAAK,EAAyB,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAE7E;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,IAAI,CAAC,UAAU,CAAC,EAAE,UAAU,EAAE,cAAc,GAAG,IAAI,CAoBvF"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { respondWithObject } from '@fedify/fedify';
|
|
2
|
+
import { federation as federationMiddleware } from '@fedify/hono';
|
|
3
|
+
import { publicDocumentAt } from '../web/documents.js';
|
|
4
|
+
import { prefersActivityStreams } from '../web/negotiate.js';
|
|
5
|
+
import { requestPath } from '../web/routes.js';
|
|
6
|
+
import { isFederatedDocument, postArticle } from './article.js';
|
|
7
|
+
/**
|
|
8
|
+
* Put Fedify in front of the rest of the app.
|
|
9
|
+
*
|
|
10
|
+
* The middleware only answers the paths Fedify registered — the actor, its
|
|
11
|
+
* collections, the post objects, WebFinger and NodeInfo; everything else falls
|
|
12
|
+
* through to `next()` and reaches the admin and the public site as before.
|
|
13
|
+
* That is why it is mounted first: the public site claims every unmatched path
|
|
14
|
+
* in its not-found handler, so anything registered after it never sees a
|
|
15
|
+
* request.
|
|
16
|
+
*
|
|
17
|
+
* A post permalink asked for as ActivityStreams is answered here too, by
|
|
18
|
+
* {@link activityStreamsDocument}, because Fedify only knows about the URLs it
|
|
19
|
+
* minted and a permalink is not one of them.
|
|
20
|
+
*
|
|
21
|
+
* The dispatchers are handed the stores off the Hono context rather than
|
|
22
|
+
* closing over them, so one federation object could serve more than one CMS
|
|
23
|
+
* and so a dispatcher is never reading a store the request did not come with.
|
|
24
|
+
*/
|
|
25
|
+
export function mountFederation(app, federation) {
|
|
26
|
+
// `@fedify/hono` types its context as the two properties it actually reads,
|
|
27
|
+
// which is looser than Hono's own; the cast is what puts `c.var` back.
|
|
28
|
+
app.use('*', federationMiddleware(federation, (context) => {
|
|
29
|
+
const c = context;
|
|
30
|
+
return contextData(c);
|
|
31
|
+
}));
|
|
32
|
+
// Named, and typed as a middleware, so the Hono context arrives with its
|
|
33
|
+
// path parameters resolved rather than as `any`.
|
|
34
|
+
const activityStreams = async (c, next) => {
|
|
35
|
+
const response = await activityStreamsDocument(c, federation);
|
|
36
|
+
if (response !== undefined)
|
|
37
|
+
return response;
|
|
38
|
+
await next();
|
|
39
|
+
};
|
|
40
|
+
app.use('*', activityStreams);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The `Article` for the post at this URL, when the request asked for
|
|
44
|
+
* ActivityStreams and the URL is a published post's permalink.
|
|
45
|
+
*
|
|
46
|
+
* doc-4 wants a peer that dereferences a link somebody shared — which is the
|
|
47
|
+
* permalink, not the object id — to land on the object rather than on a page
|
|
48
|
+
* it cannot read. The article is built through the same
|
|
49
|
+
* {@link postArticle} the object dispatcher uses, off a context Fedify makes
|
|
50
|
+
* for this request, so the two answers cannot drift apart; its `id` is the
|
|
51
|
+
* object URL either way, so a peer stores one object however it arrived.
|
|
52
|
+
*
|
|
53
|
+
* Anything else — a browser, a `.md` or `.json` request, a page, a listing —
|
|
54
|
+
* gets `undefined` and falls through to the public site untouched.
|
|
55
|
+
*/
|
|
56
|
+
async function activityStreamsDocument(c, federation) {
|
|
57
|
+
if (!prefersActivityStreams(c.req.header('accept')))
|
|
58
|
+
return undefined;
|
|
59
|
+
const document = publicDocumentAt(c.var.store, requestPath(c));
|
|
60
|
+
if (document === undefined || !isFederatedDocument(document))
|
|
61
|
+
return undefined;
|
|
62
|
+
const context = federation.createContext(c.req.raw, contextData(c));
|
|
63
|
+
return await respondWithObject(postArticle(context, document), {
|
|
64
|
+
contextLoader: context.contextLoader,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
/** What the dispatchers are handed, off the Hono context. */
|
|
68
|
+
function contextData(c) {
|
|
69
|
+
return { admin: c.var.admin, store: c.var.store, config: c.var.config };
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=mount.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mount.js","sourceRoot":"","sources":["../../src/federation/mount.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,UAAU,IAAI,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAIlE,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAGhE;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,eAAe,CAAC,GAAqB,EAAE,UAA0B;IAC/E,4EAA4E;IAC5E,uEAAuE;IACvE,GAAG,CAAC,GAAG,CACL,GAAG,EACH,oBAAoB,CAAC,UAAU,EAAE,CAAC,OAAO,EAAE,EAAE;QAC3C,MAAM,CAAC,GAAG,OAAyC,CAAC;QACpD,OAAO,WAAW,CAAC,CAAC,CAAC,CAAC;IACxB,CAAC,CAAC,CACH,CAAC;IAEF,yEAAyE;IACzE,iDAAiD;IACjD,MAAM,eAAe,GAAkC,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE;QACvE,MAAM,QAAQ,GAAG,MAAM,uBAAuB,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;QAC9D,IAAI,QAAQ,KAAK,SAAS;YAAE,OAAO,QAAQ,CAAC;QAC5C,MAAM,IAAI,EAAE,CAAC;IACf,CAAC,CAAC;IAEF,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,eAAe,CAAC,CAAC;AAChC,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,KAAK,UAAU,uBAAuB,CACpC,CAAsB,EACtB,UAA0B;IAE1B,IAAI,CAAC,sBAAsB,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAAE,OAAO,SAAS,CAAC;IAEtE,MAAM,QAAQ,GAAG,gBAAgB,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/D,IAAI,QAAQ,KAAK,SAAS,IAAI,CAAC,mBAAmB,CAAC,QAAQ,CAAC;QAAE,OAAO,SAAS,CAAC;IAE/E,MAAM,OAAO,GAAG,UAAU,CAAC,aAAa,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;IACpE,OAAO,MAAM,iBAAiB,CAAC,WAAW,CAAC,OAAO,EAAE,QAAQ,CAAC,EAAE;QAC7D,aAAa,EAAE,OAAO,CAAC,aAAa;KACrC,CAAC,CAAC;AACL,CAAC;AAED,6DAA6D;AAC7D,SAAS,WAAW,CAAC,CAAsB;IACzC,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC;AAC1E,CAAC"}
|