@dwk/activitypub 0.1.0-beta.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.
Files changed (48) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +135 -0
  3. package/dist/as2.d.ts +117 -0
  4. package/dist/as2.d.ts.map +1 -0
  5. package/dist/as2.js +174 -0
  6. package/dist/as2.js.map +1 -0
  7. package/dist/config.d.ts +148 -0
  8. package/dist/config.d.ts.map +1 -0
  9. package/dist/config.js +142 -0
  10. package/dist/config.js.map +1 -0
  11. package/dist/delivery.d.ts +43 -0
  12. package/dist/delivery.d.ts.map +1 -0
  13. package/dist/delivery.js +131 -0
  14. package/dist/delivery.js.map +1 -0
  15. package/dist/handler.d.ts +21 -0
  16. package/dist/handler.d.ts.map +1 -0
  17. package/dist/handler.js +293 -0
  18. package/dist/handler.js.map +1 -0
  19. package/dist/index.d.ts +40 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +39 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/log.d.ts +57 -0
  24. package/dist/log.d.ts.map +1 -0
  25. package/dist/log.js +53 -0
  26. package/dist/log.js.map +1 -0
  27. package/dist/nodeinfo.d.ts +33 -0
  28. package/dist/nodeinfo.d.ts.map +1 -0
  29. package/dist/nodeinfo.js +61 -0
  30. package/dist/nodeinfo.js.map +1 -0
  31. package/dist/object.d.ts +21 -0
  32. package/dist/object.d.ts.map +1 -0
  33. package/dist/object.js +722 -0
  34. package/dist/object.js.map +1 -0
  35. package/dist/signature.d.ts +108 -0
  36. package/dist/signature.d.ts.map +1 -0
  37. package/dist/signature.js +234 -0
  38. package/dist/signature.js.map +1 -0
  39. package/package.json +50 -0
  40. package/src/as2.ts +257 -0
  41. package/src/config.ts +291 -0
  42. package/src/delivery.ts +155 -0
  43. package/src/handler.ts +370 -0
  44. package/src/index.ts +90 -0
  45. package/src/log.ts +62 -0
  46. package/src/nodeinfo.ts +91 -0
  47. package/src/object.ts +883 -0
  48. package/src/signature.ts +355 -0
package/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2026 David W. Keith
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,135 @@
1
+ # `@dwk/activitypub`
2
+
3
+ > Edge-native ActivityPub actor: inbox/outbox, follower collections, signed
4
+ > server-to-server federation. Endpoint package + Durable Object.
5
+
6
+ Part of the [`@dwk` IndieWeb + Solid cohort](../../README.md). See the
7
+ [package specification](../../spec/packages/activitypub.md) for the full
8
+ requirements.
9
+
10
+ A native [ActivityPub](https://www.w3.org/TR/activitypub/) actor rooted at the
11
+ user's **own** domain — making the self-owned presence a first-class fediverse
12
+ citizen (followers, replies, boosts) rather than a bridged guest. It mirrors the
13
+ architecture proven in [`@dwk/solid-pod`](../solid-pod/README.md): a **stateless
14
+ front door** (routing + edge HTTP-signature verification) over a **per-actor
15
+ Durable Object** that is the consistency authority for activity-`id` dedup, the
16
+ follower/following/outbox collections, and the signed outbound delivery queue.
17
+ This is the second `@dwk` package to ship a Durable Object.
18
+
19
+ ## What v1 covers
20
+
21
+ - **Actor document** (`Person`) served as `application/activity+json`, with the
22
+ public key embedded inline so peers can verify this actor's signatures.
23
+ - **Collections** — `outbox`, `followers`, `following` as paged
24
+ `OrderedCollection`s; `inbox` is write-only to peers.
25
+ - **Server-to-server (inbound)** `POST /inbox`: HTTP-signature verification at
26
+ the edge, dedup by activity `id`, and handling of `Follow` / `Undo` /
27
+ `Accept` / `Create` / `Update` / `Like` / `Announce` / `Delete`.
28
+ - **Server-to-server (outbound)**: auto-`Accept` of follows and signed fan-out
29
+ delivery to follower inboxes, with retry/backoff driven by **DO alarms**.
30
+ - **NodeInfo** — the `/.well-known/nodeinfo` discovery document and a
31
+ mostly-static `nodeinfo/2.1` document (live `usage` counts pulled from the DO).
32
+ - **Owner publish endpoint** (`POST <actor>/outbox`, bearer-token gated) — the
33
+ publish → `Create` fan-out seam for [`@dwk/micropub`](../micropub/README.md).
34
+ Full client-to-server authoring is **out of scope for v1**.
35
+
36
+ ## Usage
37
+
38
+ ```ts
39
+ import { createActivityPub, ActivityPubObject } from "@dwk/activitypub";
40
+
41
+ const activitypub = createActivityPub({
42
+ baseUrl: "https://example.com",
43
+ actor: { username: "alice", name: "Alice", summary: "Hello, fediverse." },
44
+ publicKeyPem: env.AP_PUBLIC_KEY, // published in the actor document
45
+ privateKeyPem: env.AP_PRIVATE_KEY, // signs outbound deliveries (secret binding)
46
+ publishToken: env.AP_PUBLISH_TOKEN, // optional: enables POST <actor>/outbox
47
+ software: { name: "anglesite", version: "1.2.3" }, // NodeInfo
48
+ });
49
+
50
+ // In your Worker's fetch handler:
51
+ // GET /users/alice → actor document
52
+ // GET /users/alice/{outbox,followers,following}[?page=N]
53
+ // POST /users/alice/inbox → signed S2S delivery
54
+ // GET /.well-known/nodeinfo, /nodeinfo/2.1
55
+ return activitypub(request, env, ctx);
56
+
57
+ // Bind the Durable Object the package ships:
58
+ export { ActivityPubObject };
59
+ ```
60
+
61
+ ```jsonc
62
+ // wrangler.jsonc — the binding the package declares
63
+ {
64
+ "durable_objects": {
65
+ "bindings": [{ "name": "ACTOR", "class_name": "ActivityPubObject" }],
66
+ },
67
+ "migrations": [{ "tag": "v1", "new_sqlite_classes": ["ActivityPubObject"] }],
68
+ }
69
+ ```
70
+
71
+ The actor IRI and every collection IRI are derived from `baseUrl` + `username`
72
+ (`https://example.com/users/alice`, `…/inbox`, `…#main-key`). Mount the package
73
+ under a path prefix by including it in `baseUrl` (e.g.
74
+ `https://example.com/ap`) — the handler routes purely on the request URL.
75
+
76
+ ## Bindings (declared `Env` fragment)
77
+
78
+ - `ACTOR` — the Durable Object namespace for the per-actor class
79
+ (`ActivityPubObject`). The single authoritative store for dedup, collections,
80
+ and the delivery queue. The handler **fails loudly** at startup if it is
81
+ missing.
82
+
83
+ No KV is used for any authoritative state, per
84
+ [`spec/non-functional-requirements.md`](../../spec/non-functional-requirements.md):
85
+ follower lists, dedup, and the delivery queue all live in the DO's SQLite.
86
+
87
+ ## HTTP signatures
88
+
89
+ Inbound `POST /inbox` deliveries are authenticated, and outbound deliveries are
90
+ signed, with the de-facto fediverse `draft-cavage-http-signatures` profile
91
+ (RSA-SHA256 over a covered header set, body integrity via `Digest`). The
92
+ implementation is RSA-only with an explicit algorithm allow-list (no `none`, no
93
+ symmetric algorithms), mirroring the [`@dwk/dpop`](../dpop/README.md) hardening
94
+ posture.
95
+
96
+ Signing/verification sit behind the `verifyInboxSignature` config seam, so the
97
+ forthcoming cross-standard `@dwk/http-signatures` package (RFC 9421 +
98
+ draft-cavage; [#59](https://github.com/davidwkeith/workers/issues/59)) can be
99
+ swapped in unchanged once it lands.
100
+
101
+ ## Delivery safety
102
+
103
+ Outbound deliveries target attacker-influenced URLs (a follower's advertised
104
+ inbox), so every target passes a syntactic SSRF guard before any request leaves:
105
+ HTTPS only, and private / loopback / link-local / cloud-metadata hosts are
106
+ refused. (DNS rebinding is out of scope — the Workers runtime does not expose
107
+ name resolution to user code; same limitation as
108
+ [`@dwk/webmention`](../webmention/README.md)'s safe-fetch.) A `4xx` from a peer
109
+ is a permanent failure (dropped); `5xx`, `408`, `429`, and network errors are
110
+ retried with exponential backoff up to `deliveryMaxAttempts`.
111
+
112
+ ## Design
113
+
114
+ The front door is **stateless** and serves the static actor + NodeInfo documents
115
+ directly; everything that touches authoritative state is routed to the per-actor
116
+ Durable Object. Per the composition contract, the actor profile, key material,
117
+ and delivery policy are all passed into `createActivityPub` — nothing is read
118
+ from the global environment — so an actor can be instantiated multiple times and
119
+ tested in isolation.
120
+
121
+ ActivityStreams 2.0 documents are emitted in the **compact** JSON-LD form the
122
+ fediverse interoperates over (Mastodon et al.), with the AS2 + security
123
+ `@context`. Full RDF content-negotiation via [`@dwk/rdf`](../rdf/README.md) is a
124
+ future enhancement (its v1 JSON-LD subset and the AS2 context are tracked in
125
+ [`spec/open-questions.md`](../../spec/open-questions.md) §4).
126
+
127
+ ## Observability
128
+
129
+ Federation events flow through the injected `@dwk/log` `Logger`/`Metrics` seams
130
+ (default no-op): `activitypub.signature.{accepted,rejected}`,
131
+ `activitypub.inbox.{accepted,duplicate}`,
132
+ `activitypub.delivery.{succeeded,failed,blocked}`, and
133
+ `activitypub.publish.rejected`. Per the redaction policy, only reason codes,
134
+ activity types, and sanitized hosts are recorded — never key material, tokens,
135
+ or bodies.
package/dist/as2.d.ts ADDED
@@ -0,0 +1,117 @@
1
+ /**
2
+ * ActivityStreams 2.0 vocabulary helpers for `@dwk/activitypub`.
3
+ *
4
+ * ActivityStreams 2.0 documents are JSON-LD, but the entire fediverse interops
5
+ * over the **compact** form with a fixed `@context`, so this module emits and
6
+ * parses that compact JSON shape directly rather than running a full JSON-LD
7
+ * processor. (Whether `@dwk/rdf`'s v1 JSON-LD subset covers the AS2 context is
8
+ * an open question — see `spec/open-questions.md` §4 — so v1 stays on the
9
+ * compact form Mastodon and friends actually speak.)
10
+ *
11
+ * Pure data: every function takes plain values and returns plain JSON objects,
12
+ * so the vocabulary is unit-testable without a Workers runtime or any binding.
13
+ */
14
+ /** The ActivityStreams 2.0 namespace IRI. */
15
+ export declare const AS2_NS = "https://www.w3.org/ns/activitystreams";
16
+ /** The W3C security vocabulary namespace (carries `publicKey`). */
17
+ export declare const SECURITY_NS = "https://w3id.org/security/v1";
18
+ /** The special "public" collection that marks an activity world-readable. */
19
+ export declare const PUBLIC_AUDIENCE = "https://www.w3.org/ns/activitystreams#Public";
20
+ /**
21
+ * The `@context` an actor document advertises: the AS2 base plus the security
22
+ * vocabulary (so `publicKey` resolves) and the handful of property aliases
23
+ * (`manuallyApprovesFollowers`, `discoverable`) the fediverse expects.
24
+ */
25
+ export declare const ACTOR_CONTEXT: readonly unknown[];
26
+ /** Media type for an ActivityStreams 2.0 / ActivityPub document. */
27
+ export declare const AS2_CONTENT_TYPE = "application/activity+json; charset=utf-8";
28
+ /**
29
+ * The JSON-LD profile variant of AS2. A strict client may content-negotiate for
30
+ * `application/ld+json` carrying the ActivityStreams profile rather than the
31
+ * `application/activity+json` alias the fediverse speaks; both name the same
32
+ * document (§3.2).
33
+ */
34
+ export declare const AS2_LD_CONTENT_TYPE = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"; charset=utf-8";
35
+ /** Whether an `Accept` header is asking for the ActivityStreams representation. */
36
+ export declare function wantsActivityJson(accept: string | null): boolean;
37
+ /**
38
+ * Pick the AS2 media type to serve for a given `Accept` (§3.2 content
39
+ * negotiation). A peer that asks specifically for the JSON-LD profile variant
40
+ * (`application/ld+json`, without also naming `application/activity+json`) gets
41
+ * {@link AS2_LD_CONTENT_TYPE}; everyone else — plain browsers, and the
42
+ * `application/activity+json` alias the fediverse speaks — gets
43
+ * {@link AS2_CONTENT_TYPE}.
44
+ */
45
+ export declare function as2ContentType(accept: string | null): string;
46
+ /** A minimal JSON value type for AS2 documents. */
47
+ export type JsonValue = string | number | boolean | null | readonly JsonValue[] | {
48
+ readonly [key: string]: JsonValue;
49
+ };
50
+ /** A parsed ActivityStreams object/activity (compact JSON). */
51
+ export interface ActivityObject {
52
+ readonly id?: string;
53
+ readonly type?: string;
54
+ readonly actor?: JsonValue;
55
+ readonly object?: JsonValue;
56
+ readonly target?: JsonValue;
57
+ readonly to?: JsonValue;
58
+ readonly cc?: JsonValue;
59
+ readonly [key: string]: JsonValue | undefined;
60
+ }
61
+ /** The derived IRI set that identifies one actor and its collections. */
62
+ export interface ActorIris {
63
+ readonly id: string;
64
+ readonly inbox: string;
65
+ readonly outbox: string;
66
+ readonly followers: string;
67
+ readonly following: string;
68
+ readonly keyId: string;
69
+ }
70
+ /** The human-facing profile fields of an actor. */
71
+ export interface ActorProfile {
72
+ /** `preferredUsername` — the local part of the `acct:` handle. */
73
+ readonly username: string;
74
+ /** Display name; defaults to {@link username}. */
75
+ readonly name?: string;
76
+ /** Short bio rendered as `summary` (may contain HTML). */
77
+ readonly summary?: string;
78
+ /** Avatar image URL, surfaced as the actor `icon`. */
79
+ readonly icon?: string;
80
+ /** Whether follows require manual approval. Defaults to `false` (auto-accept). */
81
+ readonly manuallyApprovesFollowers?: boolean;
82
+ /** Whether the actor opts into discovery/search (Mastodon `toot:discoverable`). */
83
+ readonly discoverable?: boolean;
84
+ }
85
+ /** Optional extras woven into the actor document. */
86
+ export interface ActorDocumentOptions {
87
+ /**
88
+ * The instance-level shared inbox IRI, advertised under `endpoints` (§4.1 /
89
+ * §7.1.3) so large peers can batch-deliver to this actor. Omitted entirely
90
+ * when the deployment does not serve one.
91
+ */
92
+ readonly sharedInbox?: string;
93
+ }
94
+ /**
95
+ * Build the `Person` actor document served at the actor IRI. The public key is
96
+ * embedded inline (PEM) under the security vocabulary so verifiers can resolve
97
+ * `keyId` without a second fetch, exactly as Mastodon publishes it.
98
+ */
99
+ export declare function buildActorDocument(iris: ActorIris, profile: ActorProfile, publicKeyPem: string, options?: ActorDocumentOptions): Record<string, JsonValue>;
100
+ /**
101
+ * Build a paged `OrderedCollection` head document — the response to a bare
102
+ * collection `GET` (no `page` parameter). It advertises the total count and the
103
+ * `first`/`last` page links; the members live on the pages.
104
+ */
105
+ export declare function buildCollection(id: string, totalItems: number, pageSize: number): Record<string, JsonValue>;
106
+ /**
107
+ * Build one `OrderedCollectionPage` of members. `next` is present only when a
108
+ * further page exists, so a crawler walks `first → next → …` until it stops.
109
+ */
110
+ export declare function buildCollectionPage(collectionId: string, page: number, pageSize: number, totalItems: number, items: readonly JsonValue[]): Record<string, JsonValue>;
111
+ /** Read the `id` of an activity's `object`, whether it is an IRI or nested object. */
112
+ export declare function objectId(value: JsonValue | undefined): string | undefined;
113
+ /** Read the `type` of an activity's `object` when it is a nested object. */
114
+ export declare function objectType(value: JsonValue | undefined): string | undefined;
115
+ /** Coerce an actor reference (IRI string or embedded object) to its IRI. */
116
+ export declare function actorIri(value: JsonValue | undefined): string | undefined;
117
+ //# sourceMappingURL=as2.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"as2.d.ts","sourceRoot":"","sources":["../src/as2.ts"],"names":[],"mappings":"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"}
package/dist/as2.js ADDED
@@ -0,0 +1,174 @@
1
+ /**
2
+ * ActivityStreams 2.0 vocabulary helpers for `@dwk/activitypub`.
3
+ *
4
+ * ActivityStreams 2.0 documents are JSON-LD, but the entire fediverse interops
5
+ * over the **compact** form with a fixed `@context`, so this module emits and
6
+ * parses that compact JSON shape directly rather than running a full JSON-LD
7
+ * processor. (Whether `@dwk/rdf`'s v1 JSON-LD subset covers the AS2 context is
8
+ * an open question — see `spec/open-questions.md` §4 — so v1 stays on the
9
+ * compact form Mastodon and friends actually speak.)
10
+ *
11
+ * Pure data: every function takes plain values and returns plain JSON objects,
12
+ * so the vocabulary is unit-testable without a Workers runtime or any binding.
13
+ */
14
+ /** The ActivityStreams 2.0 namespace IRI. */
15
+ export const AS2_NS = "https://www.w3.org/ns/activitystreams";
16
+ /** The W3C security vocabulary namespace (carries `publicKey`). */
17
+ export const SECURITY_NS = "https://w3id.org/security/v1";
18
+ /** The special "public" collection that marks an activity world-readable. */
19
+ export const PUBLIC_AUDIENCE = "https://www.w3.org/ns/activitystreams#Public";
20
+ /**
21
+ * The `@context` an actor document advertises: the AS2 base plus the security
22
+ * vocabulary (so `publicKey` resolves) and the handful of property aliases
23
+ * (`manuallyApprovesFollowers`, `discoverable`) the fediverse expects.
24
+ */
25
+ export const ACTOR_CONTEXT = [
26
+ AS2_NS,
27
+ SECURITY_NS,
28
+ {
29
+ manuallyApprovesFollowers: "as:manuallyApprovesFollowers",
30
+ toot: "http://joinmastodon.org/ns#",
31
+ discoverable: "toot:discoverable",
32
+ },
33
+ ];
34
+ /** Media type for an ActivityStreams 2.0 / ActivityPub document. */
35
+ export const AS2_CONTENT_TYPE = "application/activity+json; charset=utf-8";
36
+ /**
37
+ * The JSON-LD profile variant of AS2. A strict client may content-negotiate for
38
+ * `application/ld+json` carrying the ActivityStreams profile rather than the
39
+ * `application/activity+json` alias the fediverse speaks; both name the same
40
+ * document (§3.2).
41
+ */
42
+ export const AS2_LD_CONTENT_TYPE = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"; charset=utf-8';
43
+ /**
44
+ * The media types a federation peer uses to request AS2. A `GET` whose `Accept`
45
+ * names any of these (or the matching `profile`) wants the activity JSON rather
46
+ * than an HTML profile page.
47
+ */
48
+ const AS2_ACCEPT_TOKENS = [
49
+ "application/activity+json",
50
+ "application/ld+json",
51
+ 'profile="https://www.w3.org/ns/activitystreams"',
52
+ ];
53
+ /** Whether an `Accept` header is asking for the ActivityStreams representation. */
54
+ export function wantsActivityJson(accept) {
55
+ if (!accept)
56
+ return false;
57
+ const lower = accept.toLowerCase();
58
+ return AS2_ACCEPT_TOKENS.some((token) => lower.includes(token));
59
+ }
60
+ /**
61
+ * Pick the AS2 media type to serve for a given `Accept` (§3.2 content
62
+ * negotiation). A peer that asks specifically for the JSON-LD profile variant
63
+ * (`application/ld+json`, without also naming `application/activity+json`) gets
64
+ * {@link AS2_LD_CONTENT_TYPE}; everyone else — plain browsers, and the
65
+ * `application/activity+json` alias the fediverse speaks — gets
66
+ * {@link AS2_CONTENT_TYPE}.
67
+ */
68
+ export function as2ContentType(accept) {
69
+ if (accept && wantsActivityJson(accept)) {
70
+ const lower = accept.toLowerCase();
71
+ if (lower.includes("application/ld+json") &&
72
+ !lower.includes("application/activity+json")) {
73
+ return AS2_LD_CONTENT_TYPE;
74
+ }
75
+ }
76
+ return AS2_CONTENT_TYPE;
77
+ }
78
+ /**
79
+ * Build the `Person` actor document served at the actor IRI. The public key is
80
+ * embedded inline (PEM) under the security vocabulary so verifiers can resolve
81
+ * `keyId` without a second fetch, exactly as Mastodon publishes it.
82
+ */
83
+ export function buildActorDocument(iris, profile, publicKeyPem, options = {}) {
84
+ const doc = {
85
+ "@context": ACTOR_CONTEXT,
86
+ id: iris.id,
87
+ type: "Person",
88
+ preferredUsername: profile.username,
89
+ name: profile.name ?? profile.username,
90
+ inbox: iris.inbox,
91
+ outbox: iris.outbox,
92
+ followers: iris.followers,
93
+ following: iris.following,
94
+ manuallyApprovesFollowers: profile.manuallyApprovesFollowers ?? false,
95
+ discoverable: profile.discoverable ?? true,
96
+ publicKey: {
97
+ id: iris.keyId,
98
+ owner: iris.id,
99
+ publicKeyPem,
100
+ },
101
+ };
102
+ if (profile.summary !== undefined)
103
+ doc.summary = profile.summary;
104
+ if (profile.icon !== undefined) {
105
+ doc.icon = { type: "Image", url: profile.icon };
106
+ }
107
+ if (options.sharedInbox !== undefined) {
108
+ doc.endpoints = { sharedInbox: options.sharedInbox };
109
+ }
110
+ return doc;
111
+ }
112
+ /**
113
+ * Build a paged `OrderedCollection` head document — the response to a bare
114
+ * collection `GET` (no `page` parameter). It advertises the total count and the
115
+ * `first`/`last` page links; the members live on the pages.
116
+ */
117
+ export function buildCollection(id, totalItems, pageSize) {
118
+ const doc = {
119
+ "@context": AS2_NS,
120
+ id,
121
+ type: "OrderedCollection",
122
+ totalItems,
123
+ };
124
+ if (totalItems > 0) {
125
+ doc.first = `${id}?page=1`;
126
+ doc.last = `${id}?page=${Math.max(1, Math.ceil(totalItems / pageSize))}`;
127
+ }
128
+ return doc;
129
+ }
130
+ /**
131
+ * Build one `OrderedCollectionPage` of members. `next` is present only when a
132
+ * further page exists, so a crawler walks `first → next → …` until it stops.
133
+ */
134
+ export function buildCollectionPage(collectionId, page, pageSize, totalItems, items) {
135
+ const doc = {
136
+ "@context": AS2_NS,
137
+ id: `${collectionId}?page=${page}`,
138
+ type: "OrderedCollectionPage",
139
+ partOf: collectionId,
140
+ totalItems,
141
+ orderedItems: items,
142
+ };
143
+ if (page * pageSize < totalItems) {
144
+ doc.next = `${collectionId}?page=${page + 1}`;
145
+ }
146
+ if (page > 1)
147
+ doc.prev = `${collectionId}?page=${page - 1}`;
148
+ return doc;
149
+ }
150
+ /** Read the `id` of an activity's `object`, whether it is an IRI or nested object. */
151
+ export function objectId(value) {
152
+ if (typeof value === "string")
153
+ return value;
154
+ if (value && typeof value === "object" && !Array.isArray(value)) {
155
+ const id = value.id;
156
+ if (typeof id === "string")
157
+ return id;
158
+ }
159
+ return undefined;
160
+ }
161
+ /** Read the `type` of an activity's `object` when it is a nested object. */
162
+ export function objectType(value) {
163
+ if (value && typeof value === "object" && !Array.isArray(value)) {
164
+ const type = value.type;
165
+ if (typeof type === "string")
166
+ return type;
167
+ }
168
+ return undefined;
169
+ }
170
+ /** Coerce an actor reference (IRI string or embedded object) to its IRI. */
171
+ export function actorIri(value) {
172
+ return objectId(value);
173
+ }
174
+ //# sourceMappingURL=as2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"as2.js","sourceRoot":"","sources":["../src/as2.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Configuration, the declared Cloudflare `Env` fragment, and config resolution
3
+ * for `@dwk/activitypub`.
4
+ *
5
+ * Per the composition contract the package never reads the global environment
6
+ * directly: the actor profile, key material, and delivery policy are all passed
7
+ * into {@link createActivityPub}, so an actor can be instantiated multiple times
8
+ * and unit-tested in isolation. The only runtime coupling is the per-actor
9
+ * Durable Object namespace, and a missing binding fails loudly at startup.
10
+ */
11
+ import { type Logger, type Metrics } from "@dwk/log";
12
+ import type { ActorIris, ActorProfile } from "./as2";
13
+ import type { KeyResolver, VerifyResult, InboxRequest } from "./signature";
14
+ import type { SoftwareInfo } from "./nodeinfo";
15
+ import type { ActivityPubObject } from "./object";
16
+ /** Cloudflare bindings required by the ActivityPub handler and Durable Object. */
17
+ export interface ActivityPubEnv {
18
+ /**
19
+ * Durable Object namespace for the per-actor class
20
+ * ({@link ActivityPubObject}). The single authoritative store for the inbox,
21
+ * outbox, follower/following collections, activity-`id` dedup, and the
22
+ * delivery queue.
23
+ */
24
+ readonly ACTOR: DurableObjectNamespace<ActivityPubObject>;
25
+ }
26
+ /** An override for inbound signature verification (see `@dwk/http-signatures`, #59). */
27
+ export type InboxVerifier = (request: InboxRequest) => Promise<VerifyResult> | VerifyResult;
28
+ /** Configuration passed to {@link createActivityPub}. */
29
+ export interface ActivityPubConfig {
30
+ /**
31
+ * The actor's identity root / base URL, e.g. `https://example.com`. The actor
32
+ * IRI and every collection IRI are derived from it. No trailing slash. Mount
33
+ * the package under a path prefix by including it here (e.g.
34
+ * `https://example.com/ap`).
35
+ */
36
+ readonly baseUrl: string;
37
+ /** The single actor this deployment serves (v1 is one actor per `baseUrl`). */
38
+ readonly actor: ActorProfile;
39
+ /**
40
+ * PEM-encoded SPKI **public** key, published inline in the actor document so
41
+ * peers can verify this actor's outbound signatures.
42
+ */
43
+ readonly publicKeyPem: string;
44
+ /**
45
+ * PEM-encoded PKCS#8 **private** key (a secret binding's value) used to sign
46
+ * outbound deliveries. Required to federate outbound (e.g. `Accept` a follow);
47
+ * omit it for a read-only actor that never delivers.
48
+ */
49
+ readonly privateKeyPem?: string;
50
+ /**
51
+ * Bearer token authorizing the owner-only publish endpoint
52
+ * (`POST <actor>/outbox`). When unset, that endpoint is disabled. This is the
53
+ * `@dwk/micropub` publish → `Create` fan-out seam; full C2S is out of scope
54
+ * for v1.
55
+ */
56
+ readonly publishToken?: string;
57
+ /**
58
+ * Whether to serve and advertise an instance-level **shared inbox** at
59
+ * `${baseUrl}/inbox` (ActivityPub §4.1 / §7.1.3), letting large peers
60
+ * batch-deliver to this actor. Defaults to `true`; set `false` to publish no
61
+ * `endpoints.sharedInbox` and serve no shared-inbox route.
62
+ */
63
+ readonly sharedInbox?: boolean;
64
+ /** Members served per `OrderedCollection` page. Defaults to 50. */
65
+ readonly pageSize?: number;
66
+ /** Max delivery attempts before a queued activity is dropped. Defaults to 8. */
67
+ readonly deliveryMaxAttempts?: number;
68
+ /** Base backoff (ms) for delivery retries (doubled per attempt). Defaults to 60_000. */
69
+ readonly deliveryBaseDelayMs?: number;
70
+ /** Accepted clock skew (seconds) on inbound signed `Date` headers. Defaults to 300. */
71
+ readonly clockSkewSeconds?: number;
72
+ /** Software identity for the NodeInfo document. Defaults to this package. */
73
+ readonly software?: SoftwareInfo;
74
+ /**
75
+ * Resolve a `keyId` to its owner + PEM key for inbound verification. Defaults
76
+ * to fetching the `keyId` (an actor or key IRI) as AS2 and reading
77
+ * `publicKey`. Supply your own to add caching or a key store.
78
+ */
79
+ readonly keyResolver?: KeyResolver;
80
+ /**
81
+ * Override inbound signature verification entirely (e.g. to plug in
82
+ * `@dwk/http-signatures`). When set, the built-in draft-cavage verifier and
83
+ * {@link keyResolver} are bypassed.
84
+ */
85
+ readonly verifyInboxSignature?: InboxVerifier;
86
+ /** `fetch` used for key resolution and outbound delivery. Defaults to global `fetch`. */
87
+ readonly fetch?: typeof fetch;
88
+ /** Injectable clock (epoch ms) for deterministic tests. Defaults to `Date.now`. */
89
+ readonly now?: () => number;
90
+ /** Structured-logging seam; defaults to a no-op. */
91
+ readonly logger?: Logger;
92
+ /** Metrics seam; defaults to a no-op. */
93
+ readonly metrics?: Metrics;
94
+ }
95
+ /** Fully-resolved configuration with defaults applied and IRIs derived. */
96
+ export interface ResolvedConfig {
97
+ readonly baseUrl: string;
98
+ readonly actor: ActorProfile;
99
+ readonly iris: ActorIris;
100
+ /** Instance-level shared inbox IRI, or `undefined` when not served. */
101
+ readonly sharedInbox?: string;
102
+ readonly publicKeyPem: string;
103
+ readonly privateKeyPem?: string;
104
+ readonly publishToken?: string;
105
+ readonly pageSize: number;
106
+ readonly deliveryMaxAttempts: number;
107
+ readonly deliveryBaseDelayMs: number;
108
+ readonly clockSkewSeconds: number;
109
+ readonly software: SoftwareInfo;
110
+ readonly keyResolver: KeyResolver;
111
+ readonly verifyInboxSignature?: InboxVerifier;
112
+ readonly fetch: typeof fetch;
113
+ readonly now: () => number;
114
+ readonly logger: Logger;
115
+ readonly metrics: Metrics;
116
+ }
117
+ /**
118
+ * Internal headers the trusted front door uses to hand verified facts and the
119
+ * config subset the DO needs (including signing key material, which never leaves
120
+ * Cloudflare's network) to the Durable Object.
121
+ */
122
+ export declare const INTERNAL_HEADERS: {
123
+ /** The verified actor IRI that signed an inbound `POST /inbox` (absent ⇒ unverified). */
124
+ readonly signedActor: "x-ap-signed-actor";
125
+ /** JSON config subset the DO needs (IRIs, delivery policy, signing key). */
126
+ readonly config: "x-ap-config";
127
+ /** Marks an owner-authorized publish request (`POST <actor>/outbox`). */
128
+ readonly publish: "x-ap-publish";
129
+ };
130
+ /** The config subset the front door forwards to the DO via {@link INTERNAL_HEADERS.config}. */
131
+ export interface ForwardedConfig {
132
+ readonly iris: ActorIris;
133
+ readonly actorName: string;
134
+ /** Shared inbox IRI the DO should also accept inbound `POST`s on, if served. */
135
+ readonly sharedInbox?: string;
136
+ readonly manuallyApprovesFollowers: boolean;
137
+ readonly pageSize: number;
138
+ readonly deliveryMaxAttempts: number;
139
+ readonly deliveryBaseDelayMs: number;
140
+ readonly keyId: string;
141
+ /** Private key (PKCS#8 PEM) so the DO can sign deliveries from its alarm. */
142
+ readonly privateKeyPem?: string;
143
+ }
144
+ /** Derive the actor + collection IRIs from a base URL and username. */
145
+ export declare function deriveIris(baseUrl: string, username: string): ActorIris;
146
+ /** Apply defaults and derive IRIs from raw {@link ActivityPubConfig}. */
147
+ export declare function resolveConfig(config: ActivityPubConfig): ResolvedConfig;
148
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}