@fedify/relay 2.0.0-pr.471.1
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 +20 -0
- package/README.md +257 -0
- package/dist/mod.cjs +229 -0
- package/dist/mod.d.cts +59 -0
- package/dist/mod.d.ts +59 -0
- package/dist/mod.js +205 -0
- package/package.json +64 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright 2024–2025 Hong Minhee
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
|
7
|
+
the Software without restriction, including without limitation the rights to
|
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
|
10
|
+
subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
<!-- deno-fmt-ignore-file -->
|
|
2
|
+
|
|
3
|
+
@fedify/relay: ActivityPub relay for Fedify
|
|
4
|
+
============================================
|
|
5
|
+
|
|
6
|
+
[![JSR][JSR badge]][JSR]
|
|
7
|
+
[![npm][npm badge]][npm]
|
|
8
|
+
[![Follow @fedify@hollo.social][@fedify@hollo.social badge]][@fedify@hollo.social]
|
|
9
|
+
|
|
10
|
+
*This package is available since Fedify 2.0.0.*
|
|
11
|
+
|
|
12
|
+
This package provides ActivityPub relay functionality for the [Fedify]
|
|
13
|
+
ecosystem, enabling the creation and management of relay servers that can
|
|
14
|
+
forward activities between federated instances.
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
What is an ActivityPub relay?
|
|
18
|
+
------------------------------
|
|
19
|
+
|
|
20
|
+
ActivityPub relays are infrastructure components that help small instances
|
|
21
|
+
participate effectively in the federated social network by acting as
|
|
22
|
+
intermediary servers that distribute public content without requiring
|
|
23
|
+
individual actor-following relationships. When an instance subscribes to
|
|
24
|
+
a relay, all public posts from that instance are forwarded to all other
|
|
25
|
+
subscribed instances, creating a shared pool of federated content.
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
Relay protocols
|
|
29
|
+
---------------
|
|
30
|
+
|
|
31
|
+
This package supports two popular relay protocols used in the fediverse:
|
|
32
|
+
|
|
33
|
+
### Mastodon-style relay
|
|
34
|
+
|
|
35
|
+
The Mastodon-style relay protocol uses LD signatures for activity
|
|
36
|
+
verification and follows the Public collection. This protocol is widely
|
|
37
|
+
supported by Mastodon and many other ActivityPub implementations.
|
|
38
|
+
|
|
39
|
+
Key features:
|
|
40
|
+
|
|
41
|
+
- Direct activity relaying with proper content types (`Create`, `Update`,
|
|
42
|
+
`Delete`, `Move`)
|
|
43
|
+
- LD signature verification and generation
|
|
44
|
+
- Follows the ActivityPub Public collection
|
|
45
|
+
- Simple subscription mechanism via `Follow` activities
|
|
46
|
+
|
|
47
|
+
### LitePub-style relay
|
|
48
|
+
|
|
49
|
+
*LitePub relay support is planned for a future release.*
|
|
50
|
+
|
|
51
|
+
The LitePub-style relay protocol uses bidirectional following relationships
|
|
52
|
+
and wraps activities in `Announce` activities for distribution.
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
Installation
|
|
56
|
+
------------
|
|
57
|
+
|
|
58
|
+
::: code-group
|
|
59
|
+
|
|
60
|
+
~~~~ sh [Deno]
|
|
61
|
+
deno add jsr:@fedify/relay
|
|
62
|
+
~~~~
|
|
63
|
+
|
|
64
|
+
~~~~ sh [npm]
|
|
65
|
+
npm add @fedify/relay
|
|
66
|
+
~~~~
|
|
67
|
+
|
|
68
|
+
~~~~ sh [pnpm]
|
|
69
|
+
pnpm add @fedify/relay
|
|
70
|
+
~~~~
|
|
71
|
+
|
|
72
|
+
~~~~ sh [Yarn]
|
|
73
|
+
yarn add @fedify/relay
|
|
74
|
+
~~~~
|
|
75
|
+
|
|
76
|
+
~~~~ sh [Bun]
|
|
77
|
+
bun add @fedify/relay
|
|
78
|
+
~~~~
|
|
79
|
+
|
|
80
|
+
:::
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
Usage
|
|
84
|
+
-----
|
|
85
|
+
|
|
86
|
+
### Creating a Mastodon-style relay
|
|
87
|
+
|
|
88
|
+
Here's a simple example of creating a Mastodon-compatible relay server:
|
|
89
|
+
|
|
90
|
+
~~~~ typescript
|
|
91
|
+
import { MastodonRelay } from "@fedify/relay";
|
|
92
|
+
import { MemoryKvStore } from "@fedify/fedify";
|
|
93
|
+
|
|
94
|
+
const relay = new MastodonRelay({
|
|
95
|
+
kv: new MemoryKvStore(),
|
|
96
|
+
domain: "relay.example.com",
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Optional: Set a custom subscription handler to approve/reject subscriptions
|
|
100
|
+
relay.setSubscriptionHandler(async (ctx, actor) => {
|
|
101
|
+
// Implement your approval logic here
|
|
102
|
+
// Return true to approve, false to reject
|
|
103
|
+
const domain = new URL(actor.id!).hostname;
|
|
104
|
+
const blockedDomains = ["spam.example", "blocked.example"];
|
|
105
|
+
return !blockedDomains.includes(domain);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Serve the relay
|
|
109
|
+
Deno.serve((request) => relay.fetch(request));
|
|
110
|
+
~~~~
|
|
111
|
+
|
|
112
|
+
### Subscription handling
|
|
113
|
+
|
|
114
|
+
By default, the relay automatically rejects all subscription requests.
|
|
115
|
+
You can customize this behavior by setting a subscription handler:
|
|
116
|
+
|
|
117
|
+
~~~~ typescript
|
|
118
|
+
relay.setSubscriptionHandler(async (ctx, actor) => {
|
|
119
|
+
// Example: Only allow subscriptions from specific domains
|
|
120
|
+
const domain = new URL(actor.id!).hostname;
|
|
121
|
+
const allowedDomains = ["mastodon.social", "fosstodon.org"];
|
|
122
|
+
return allowedDomains.includes(domain);
|
|
123
|
+
});
|
|
124
|
+
~~~~
|
|
125
|
+
|
|
126
|
+
### Integration with web frameworks
|
|
127
|
+
|
|
128
|
+
The relay's `fetch()` method returns a standard `Response` object, making it
|
|
129
|
+
compatible with any web framework that supports the Fetch API. Here's an
|
|
130
|
+
example with Hono:
|
|
131
|
+
|
|
132
|
+
~~~~ typescript
|
|
133
|
+
import { Hono } from "hono";
|
|
134
|
+
import { MastodonRelay } from "@fedify/relay";
|
|
135
|
+
import { MemoryKvStore } from "@fedify/fedify";
|
|
136
|
+
|
|
137
|
+
const app = new Hono();
|
|
138
|
+
const relay = new MastodonRelay({
|
|
139
|
+
kv: new MemoryKvStore(),
|
|
140
|
+
domain: "relay.example.com",
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
app.use("*", async (c) => {
|
|
144
|
+
return await relay.fetch(c.req.raw);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
export default app;
|
|
148
|
+
~~~~
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
How it works
|
|
152
|
+
------------
|
|
153
|
+
|
|
154
|
+
The relay operates by:
|
|
155
|
+
|
|
156
|
+
1. **Actor registration**: The relay presents itself as a Service actor at
|
|
157
|
+
`/users/relay`
|
|
158
|
+
2. **Subscription**: Instances subscribe to the relay by sending a `Follow`
|
|
159
|
+
activity
|
|
160
|
+
3. **Approval**: The relay's subscription handler determines whether to
|
|
161
|
+
approve the subscription (responds with `Accept` or `Reject`)
|
|
162
|
+
4. **Forwarding**: When a subscribed instance sends activities (`Create`,
|
|
163
|
+
`Update`, `Delete`, `Move`) to the relay's inbox, the relay forwards them
|
|
164
|
+
to all other subscribed instances
|
|
165
|
+
5. **Unsubscription**: Instances can unsubscribe by sending an `Undo` activity
|
|
166
|
+
wrapping their original `Follow` activity
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
Storage requirements
|
|
170
|
+
--------------------
|
|
171
|
+
|
|
172
|
+
The relay requires a key–value store to persist:
|
|
173
|
+
|
|
174
|
+
- Subscriber list and their Follow activity IDs
|
|
175
|
+
- Subscriber actor information
|
|
176
|
+
- Relay's cryptographic key pairs (RSA and Ed25519)
|
|
177
|
+
|
|
178
|
+
Any `KvStore` implementation from Fedify can be used, including:
|
|
179
|
+
|
|
180
|
+
- `MemoryKvStore` (for development/testing)
|
|
181
|
+
- `DenoKvStore` (Deno KV)
|
|
182
|
+
- `RedisKvStore` (Redis)
|
|
183
|
+
- `PostgresKvStore` (PostgreSQL)
|
|
184
|
+
- `SqliteKvStore` (SQLite)
|
|
185
|
+
|
|
186
|
+
For production use, choose a persistent storage backend like Redis or
|
|
187
|
+
PostgreSQL. See the [Fedify documentation on key–value stores] for more
|
|
188
|
+
details.
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
API reference
|
|
192
|
+
-------------
|
|
193
|
+
|
|
194
|
+
### `MastodonRelay`
|
|
195
|
+
|
|
196
|
+
A Mastodon-compatible ActivityPub relay implementation.
|
|
197
|
+
|
|
198
|
+
#### Constructor
|
|
199
|
+
|
|
200
|
+
~~~~ typescript
|
|
201
|
+
new MastodonRelay(options: RelayOptions)
|
|
202
|
+
~~~~
|
|
203
|
+
|
|
204
|
+
#### Properties
|
|
205
|
+
|
|
206
|
+
- `domain`: The relay's domain name (read-only)
|
|
207
|
+
|
|
208
|
+
#### Methods
|
|
209
|
+
|
|
210
|
+
- `fetch(request: Request): Promise<Response>`: Handle incoming HTTP requests
|
|
211
|
+
- `setSubscriptionHandler(handler: SubscriptionRequestHandler): this`:
|
|
212
|
+
Set a custom handler for subscription approval/rejection
|
|
213
|
+
|
|
214
|
+
### `RelayOptions`
|
|
215
|
+
|
|
216
|
+
Configuration options for the relay:
|
|
217
|
+
|
|
218
|
+
- `kv: KvStore` (required): Key–value store for persisting relay data
|
|
219
|
+
- `domain?: string`: Relay's domain name (defaults to `"localhost"`)
|
|
220
|
+
- `documentLoaderFactory?: DocumentLoaderFactory`: Custom document loader
|
|
221
|
+
factory
|
|
222
|
+
- `authenticatedDocumentLoaderFactory?: AuthenticatedDocumentLoaderFactory`:
|
|
223
|
+
Custom authenticated document loader factory
|
|
224
|
+
- `federation?: Federation<void>`: Custom Federation instance (for advanced
|
|
225
|
+
use cases)
|
|
226
|
+
- `queue?: MessageQueue`: Message queue for background activity processing
|
|
227
|
+
|
|
228
|
+
### `SubscriptionRequestHandler`
|
|
229
|
+
|
|
230
|
+
A function that determines whether to approve a subscription request:
|
|
231
|
+
|
|
232
|
+
~~~~ typescript
|
|
233
|
+
type SubscriptionRequestHandler = (
|
|
234
|
+
ctx: Context<void>,
|
|
235
|
+
clientActor: Actor,
|
|
236
|
+
) => Promise<boolean>
|
|
237
|
+
~~~~
|
|
238
|
+
|
|
239
|
+
Parameters:
|
|
240
|
+
|
|
241
|
+
- `ctx`: The Fedify context object
|
|
242
|
+
- `clientActor`: The actor requesting to subscribe
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
|
|
246
|
+
- `true` to approve the subscription
|
|
247
|
+
- `false` to reject the subscription
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
[JSR]: https://jsr.io/@fedify/relay
|
|
251
|
+
[JSR badge]: https://jsr.io/badges/@fedify/relay
|
|
252
|
+
[npm]: https://www.npmjs.com/package/@fedify/relay
|
|
253
|
+
[npm badge]: https://img.shields.io/npm/v/@fedify/relay?logo=npm
|
|
254
|
+
[@fedify@hollo.social badge]: https://fedi-badge.deno.dev/@fedify@hollo.social/followers.svg
|
|
255
|
+
[@fedify@hollo.social]: https://hollo.social/@fedify
|
|
256
|
+
[Fedify]: https://fedify.dev/
|
|
257
|
+
[Fedify documentation on key–value stores]: https://fedify.dev/manual/kv
|
package/dist/mod.cjs
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
//#region rolldown:runtime
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
10
|
+
key = keys[i];
|
|
11
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
12
|
+
get: ((k) => from[k]).bind(null, key),
|
|
13
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
19
|
+
value: mod,
|
|
20
|
+
enumerable: true
|
|
21
|
+
}) : target, mod));
|
|
22
|
+
|
|
23
|
+
//#endregion
|
|
24
|
+
const __fedify_fedify = __toESM(require("@fedify/fedify"));
|
|
25
|
+
const __fedify_fedify_vocab = __toESM(require("@fedify/fedify/vocab"));
|
|
26
|
+
|
|
27
|
+
//#region src/relay.ts
|
|
28
|
+
const RELAY_SERVER_ACTOR = "relay";
|
|
29
|
+
/**
|
|
30
|
+
* A Mastodon-compatible ActivityPub relay implementation.
|
|
31
|
+
* This relay follows Mastodon's relay protocol for maximum compatibility
|
|
32
|
+
* with Mastodon instances.
|
|
33
|
+
*
|
|
34
|
+
* @since 2.0.0
|
|
35
|
+
*/
|
|
36
|
+
var MastodonRelay = class {
|
|
37
|
+
#federation;
|
|
38
|
+
#options;
|
|
39
|
+
#subscriptionHandler;
|
|
40
|
+
constructor(options) {
|
|
41
|
+
this.#options = options;
|
|
42
|
+
this.#federation = options.federation ?? (0, __fedify_fedify.createFederation)({
|
|
43
|
+
kv: options.kv,
|
|
44
|
+
queue: options.queue,
|
|
45
|
+
documentLoaderFactory: options.documentLoaderFactory,
|
|
46
|
+
authenticatedDocumentLoaderFactory: options.authenticatedDocumentLoaderFactory
|
|
47
|
+
});
|
|
48
|
+
this.#federation.setActorDispatcher("/users/{identifier}", async (ctx, identifier) => {
|
|
49
|
+
if (identifier !== RELAY_SERVER_ACTOR) return null;
|
|
50
|
+
const keys = await ctx.getActorKeyPairs(identifier);
|
|
51
|
+
return new __fedify_fedify_vocab.Service({
|
|
52
|
+
id: ctx.getActorUri(identifier),
|
|
53
|
+
preferredUsername: identifier,
|
|
54
|
+
name: "ActivityPub Relay",
|
|
55
|
+
summary: "Mastodon-compatible ActivityPub relay server",
|
|
56
|
+
inbox: ctx.getInboxUri(),
|
|
57
|
+
followers: ctx.getFollowersUri(identifier),
|
|
58
|
+
url: ctx.getActorUri(identifier),
|
|
59
|
+
publicKey: keys[0].cryptographicKey,
|
|
60
|
+
assertionMethods: keys.map((k) => k.multikey)
|
|
61
|
+
});
|
|
62
|
+
}).setKeyPairsDispatcher(async (_ctx, identifier) => {
|
|
63
|
+
if (identifier !== RELAY_SERVER_ACTOR) return [];
|
|
64
|
+
const rsaPairJson = await options.kv.get([
|
|
65
|
+
"keypair",
|
|
66
|
+
"rsa",
|
|
67
|
+
identifier
|
|
68
|
+
]);
|
|
69
|
+
const ed25519PairJson = await options.kv.get([
|
|
70
|
+
"keypair",
|
|
71
|
+
"ed25519",
|
|
72
|
+
identifier
|
|
73
|
+
]);
|
|
74
|
+
if (rsaPairJson == null || ed25519PairJson == null) {
|
|
75
|
+
const rsaPair$1 = await (0, __fedify_fedify.generateCryptoKeyPair)("RSASSA-PKCS1-v1_5");
|
|
76
|
+
const ed25519Pair$1 = await (0, __fedify_fedify.generateCryptoKeyPair)("Ed25519");
|
|
77
|
+
await options.kv.set([
|
|
78
|
+
"keypair",
|
|
79
|
+
"rsa",
|
|
80
|
+
identifier
|
|
81
|
+
], {
|
|
82
|
+
privateKey: await (0, __fedify_fedify.exportJwk)(rsaPair$1.privateKey),
|
|
83
|
+
publicKey: await (0, __fedify_fedify.exportJwk)(rsaPair$1.publicKey)
|
|
84
|
+
});
|
|
85
|
+
await options.kv.set([
|
|
86
|
+
"keypair",
|
|
87
|
+
"ed25519",
|
|
88
|
+
identifier
|
|
89
|
+
], {
|
|
90
|
+
privateKey: await (0, __fedify_fedify.exportJwk)(ed25519Pair$1.privateKey),
|
|
91
|
+
publicKey: await (0, __fedify_fedify.exportJwk)(ed25519Pair$1.publicKey)
|
|
92
|
+
});
|
|
93
|
+
return [rsaPair$1, ed25519Pair$1];
|
|
94
|
+
}
|
|
95
|
+
const rsaPair = {
|
|
96
|
+
privateKey: await (0, __fedify_fedify.importJwk)(rsaPairJson.privateKey, "private"),
|
|
97
|
+
publicKey: await (0, __fedify_fedify.importJwk)(rsaPairJson.publicKey, "public")
|
|
98
|
+
};
|
|
99
|
+
const ed25519Pair = {
|
|
100
|
+
privateKey: await (0, __fedify_fedify.importJwk)(ed25519PairJson.privateKey, "private"),
|
|
101
|
+
publicKey: await (0, __fedify_fedify.importJwk)(ed25519PairJson.publicKey, "public")
|
|
102
|
+
};
|
|
103
|
+
return [rsaPair, ed25519Pair];
|
|
104
|
+
});
|
|
105
|
+
this.#federation.setFollowersDispatcher("/users/{identifier}/followers", async (_ctx, identifier) => {
|
|
106
|
+
if (identifier !== RELAY_SERVER_ACTOR) return null;
|
|
107
|
+
const activityIds = await options.kv.get(["followers"]) ?? [];
|
|
108
|
+
const actors = [];
|
|
109
|
+
for (const activityId of activityIds) {
|
|
110
|
+
const actorJson = await options.kv.get(["follower", activityId]);
|
|
111
|
+
const actor = await __fedify_fedify_vocab.Object.fromJsonLd(actorJson);
|
|
112
|
+
if (!(0, __fedify_fedify_vocab.isActor)(actor)) continue;
|
|
113
|
+
actors.push(actor);
|
|
114
|
+
}
|
|
115
|
+
return { items: actors };
|
|
116
|
+
});
|
|
117
|
+
this.#federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(__fedify_fedify_vocab.Follow, async (ctx, follow) => {
|
|
118
|
+
if (follow.id == null || follow.objectId == null) return;
|
|
119
|
+
const parsed = ctx.parseUri(follow.objectId);
|
|
120
|
+
const isPublicFollow = follow.objectId.href === "https://www.w3.org/ns/activitystreams#Public";
|
|
121
|
+
if (!isPublicFollow && parsed?.type !== "actor") return;
|
|
122
|
+
const relayActorUri = ctx.getActorUri(RELAY_SERVER_ACTOR);
|
|
123
|
+
const recipient = await follow.getActor(ctx);
|
|
124
|
+
if (recipient == null || recipient.id == null || recipient.preferredUsername == null || recipient.inboxId == null) return;
|
|
125
|
+
let approved = false;
|
|
126
|
+
if (this.#subscriptionHandler) approved = await this.#subscriptionHandler(ctx, recipient);
|
|
127
|
+
if (approved) {
|
|
128
|
+
const followers = await options.kv.get(["followers"]) ?? [];
|
|
129
|
+
followers.push(follow.id.href);
|
|
130
|
+
await options.kv.set(["followers"], followers);
|
|
131
|
+
await options.kv.set(["follower", follow.id.href], await recipient.toJsonLd());
|
|
132
|
+
await ctx.sendActivity({ identifier: RELAY_SERVER_ACTOR }, recipient, new __fedify_fedify_vocab.Accept({
|
|
133
|
+
id: new URL(`#accepts`, relayActorUri),
|
|
134
|
+
actor: relayActorUri,
|
|
135
|
+
object: follow
|
|
136
|
+
}));
|
|
137
|
+
} else await ctx.sendActivity({ identifier: RELAY_SERVER_ACTOR }, recipient, new __fedify_fedify_vocab.Reject({
|
|
138
|
+
id: new URL(`#rejects`, relayActorUri),
|
|
139
|
+
actor: relayActorUri,
|
|
140
|
+
object: follow
|
|
141
|
+
}));
|
|
142
|
+
}).on(__fedify_fedify_vocab.Undo, async (ctx, undo) => {
|
|
143
|
+
const activity = await undo.getObject(ctx);
|
|
144
|
+
if (activity instanceof __fedify_fedify_vocab.Follow) {
|
|
145
|
+
if (activity.id == null || activity.actorId == null) return;
|
|
146
|
+
const activityId = activity.id.href;
|
|
147
|
+
const followers = await options.kv.get(["followers"]) ?? [];
|
|
148
|
+
const updatedFollowers = followers.filter((id) => id !== activityId);
|
|
149
|
+
await options.kv.set(["followers"], updatedFollowers);
|
|
150
|
+
options.kv.delete(["follower", activityId]);
|
|
151
|
+
} else console.warn("Unsupported object type ({type}) for Undo activity: {object}", {
|
|
152
|
+
type: activity?.constructor.name,
|
|
153
|
+
object: activity
|
|
154
|
+
});
|
|
155
|
+
}).on(__fedify_fedify_vocab.Create, async (ctx, create) => {
|
|
156
|
+
const sender = await create.getActor(ctx);
|
|
157
|
+
const excludeBaseUris = sender?.id ? [new URL(sender.id)] : [];
|
|
158
|
+
await ctx.forwardActivity({ identifier: RELAY_SERVER_ACTOR }, "followers", {
|
|
159
|
+
skipIfUnsigned: true,
|
|
160
|
+
excludeBaseUris,
|
|
161
|
+
preferSharedInbox: true
|
|
162
|
+
});
|
|
163
|
+
}).on(__fedify_fedify_vocab.Delete, async (ctx, deleteActivity) => {
|
|
164
|
+
const sender = await deleteActivity.getActor(ctx);
|
|
165
|
+
const excludeBaseUris = sender?.id ? [new URL(sender.id)] : [];
|
|
166
|
+
await ctx.forwardActivity({ identifier: RELAY_SERVER_ACTOR }, "followers", {
|
|
167
|
+
skipIfUnsigned: true,
|
|
168
|
+
excludeBaseUris,
|
|
169
|
+
preferSharedInbox: true
|
|
170
|
+
});
|
|
171
|
+
}).on(__fedify_fedify_vocab.Move, async (ctx, deleteActivity) => {
|
|
172
|
+
const sender = await deleteActivity.getActor(ctx);
|
|
173
|
+
const excludeBaseUris = sender?.id ? [new URL(sender.id)] : [];
|
|
174
|
+
await ctx.forwardActivity({ identifier: RELAY_SERVER_ACTOR }, "followers", {
|
|
175
|
+
skipIfUnsigned: true,
|
|
176
|
+
excludeBaseUris,
|
|
177
|
+
preferSharedInbox: true
|
|
178
|
+
});
|
|
179
|
+
}).on(__fedify_fedify_vocab.Update, async (ctx, deleteActivity) => {
|
|
180
|
+
const sender = await deleteActivity.getActor(ctx);
|
|
181
|
+
const excludeBaseUris = sender?.id ? [new URL(sender.id)] : [];
|
|
182
|
+
await ctx.forwardActivity({ identifier: RELAY_SERVER_ACTOR }, "followers", {
|
|
183
|
+
skipIfUnsigned: true,
|
|
184
|
+
excludeBaseUris,
|
|
185
|
+
preferSharedInbox: true
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
get domain() {
|
|
190
|
+
return this.#options.domain || "localhost";
|
|
191
|
+
}
|
|
192
|
+
fetch(request) {
|
|
193
|
+
return this.#federation.fetch(request, { contextData: void 0 });
|
|
194
|
+
}
|
|
195
|
+
setSubscriptionHandler(handler) {
|
|
196
|
+
this.#subscriptionHandler = handler;
|
|
197
|
+
return this;
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
/**
|
|
201
|
+
* A LitePub-compatible ActivityPub relay implementation.
|
|
202
|
+
* This relay follows LitePub's relay protocol and extensions for
|
|
203
|
+
* enhanced federation capabilities.
|
|
204
|
+
*
|
|
205
|
+
* @since 2.0.0
|
|
206
|
+
*/
|
|
207
|
+
var LitePubRelay = class {
|
|
208
|
+
#federation;
|
|
209
|
+
#options;
|
|
210
|
+
#subscriptionHandler;
|
|
211
|
+
constructor(options) {
|
|
212
|
+
this.#options = options;
|
|
213
|
+
this.#federation = (0, __fedify_fedify.createFederation)({ kv: options.kv });
|
|
214
|
+
}
|
|
215
|
+
get domain() {
|
|
216
|
+
return this.#options.domain || "localhost";
|
|
217
|
+
}
|
|
218
|
+
fetch(request) {
|
|
219
|
+
return this.#federation.fetch(request, { contextData: void 0 });
|
|
220
|
+
}
|
|
221
|
+
setSubscriptionHandler(handler) {
|
|
222
|
+
this.#subscriptionHandler = handler;
|
|
223
|
+
return this;
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
//#endregion
|
|
228
|
+
exports.LitePubRelay = LitePubRelay;
|
|
229
|
+
exports.MastodonRelay = MastodonRelay;
|
package/dist/mod.d.cts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Context, Federation, KvStore, MessageQueue } from "@fedify/fedify";
|
|
2
|
+
import { Actor } from "@fedify/fedify/vocab";
|
|
3
|
+
import { AuthenticatedDocumentLoaderFactory, DocumentLoaderFactory } from "@fedify/vocab-runtime";
|
|
4
|
+
|
|
5
|
+
//#region src/relay.d.ts
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Handler for subscription requests (Follow/Undo activities).
|
|
9
|
+
*/
|
|
10
|
+
type SubscriptionRequestHandler = (ctx: Context<void>, clientActor: Actor) => Promise<boolean>;
|
|
11
|
+
/**
|
|
12
|
+
* Configuration options for the ActivityPub relay.
|
|
13
|
+
*/
|
|
14
|
+
interface RelayOptions {
|
|
15
|
+
kv: KvStore;
|
|
16
|
+
domain?: string;
|
|
17
|
+
documentLoaderFactory?: DocumentLoaderFactory;
|
|
18
|
+
authenticatedDocumentLoaderFactory?: AuthenticatedDocumentLoaderFactory;
|
|
19
|
+
federation?: Federation<void>;
|
|
20
|
+
queue?: MessageQueue;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Base interface for ActivityPub relay implementations.
|
|
24
|
+
*/
|
|
25
|
+
interface Relay {
|
|
26
|
+
readonly domain: string;
|
|
27
|
+
fetch(request: Request): Promise<Response>;
|
|
28
|
+
setSubscriptionHandler(handler: SubscriptionRequestHandler): this;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* A Mastodon-compatible ActivityPub relay implementation.
|
|
32
|
+
* This relay follows Mastodon's relay protocol for maximum compatibility
|
|
33
|
+
* with Mastodon instances.
|
|
34
|
+
*
|
|
35
|
+
* @since 2.0.0
|
|
36
|
+
*/
|
|
37
|
+
declare class MastodonRelay implements Relay {
|
|
38
|
+
#private;
|
|
39
|
+
constructor(options: RelayOptions);
|
|
40
|
+
get domain(): string;
|
|
41
|
+
fetch(request: Request): Promise<Response>;
|
|
42
|
+
setSubscriptionHandler(handler: SubscriptionRequestHandler): this;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* A LitePub-compatible ActivityPub relay implementation.
|
|
46
|
+
* This relay follows LitePub's relay protocol and extensions for
|
|
47
|
+
* enhanced federation capabilities.
|
|
48
|
+
*
|
|
49
|
+
* @since 2.0.0
|
|
50
|
+
*/
|
|
51
|
+
declare class LitePubRelay implements Relay {
|
|
52
|
+
#private;
|
|
53
|
+
constructor(options: RelayOptions);
|
|
54
|
+
get domain(): string;
|
|
55
|
+
fetch(request: Request): Promise<Response>;
|
|
56
|
+
setSubscriptionHandler(handler: SubscriptionRequestHandler): this;
|
|
57
|
+
}
|
|
58
|
+
//#endregion
|
|
59
|
+
export { LitePubRelay, MastodonRelay, Relay, RelayOptions };
|
package/dist/mod.d.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Context, Federation, KvStore, MessageQueue } from "@fedify/fedify";
|
|
2
|
+
import { Actor } from "@fedify/fedify/vocab";
|
|
3
|
+
import { AuthenticatedDocumentLoaderFactory, DocumentLoaderFactory } from "@fedify/vocab-runtime";
|
|
4
|
+
|
|
5
|
+
//#region src/relay.d.ts
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Handler for subscription requests (Follow/Undo activities).
|
|
9
|
+
*/
|
|
10
|
+
type SubscriptionRequestHandler = (ctx: Context<void>, clientActor: Actor) => Promise<boolean>;
|
|
11
|
+
/**
|
|
12
|
+
* Configuration options for the ActivityPub relay.
|
|
13
|
+
*/
|
|
14
|
+
interface RelayOptions {
|
|
15
|
+
kv: KvStore;
|
|
16
|
+
domain?: string;
|
|
17
|
+
documentLoaderFactory?: DocumentLoaderFactory;
|
|
18
|
+
authenticatedDocumentLoaderFactory?: AuthenticatedDocumentLoaderFactory;
|
|
19
|
+
federation?: Federation<void>;
|
|
20
|
+
queue?: MessageQueue;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Base interface for ActivityPub relay implementations.
|
|
24
|
+
*/
|
|
25
|
+
interface Relay {
|
|
26
|
+
readonly domain: string;
|
|
27
|
+
fetch(request: Request): Promise<Response>;
|
|
28
|
+
setSubscriptionHandler(handler: SubscriptionRequestHandler): this;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* A Mastodon-compatible ActivityPub relay implementation.
|
|
32
|
+
* This relay follows Mastodon's relay protocol for maximum compatibility
|
|
33
|
+
* with Mastodon instances.
|
|
34
|
+
*
|
|
35
|
+
* @since 2.0.0
|
|
36
|
+
*/
|
|
37
|
+
declare class MastodonRelay implements Relay {
|
|
38
|
+
#private;
|
|
39
|
+
constructor(options: RelayOptions);
|
|
40
|
+
get domain(): string;
|
|
41
|
+
fetch(request: Request): Promise<Response>;
|
|
42
|
+
setSubscriptionHandler(handler: SubscriptionRequestHandler): this;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* A LitePub-compatible ActivityPub relay implementation.
|
|
46
|
+
* This relay follows LitePub's relay protocol and extensions for
|
|
47
|
+
* enhanced federation capabilities.
|
|
48
|
+
*
|
|
49
|
+
* @since 2.0.0
|
|
50
|
+
*/
|
|
51
|
+
declare class LitePubRelay implements Relay {
|
|
52
|
+
#private;
|
|
53
|
+
constructor(options: RelayOptions);
|
|
54
|
+
get domain(): string;
|
|
55
|
+
fetch(request: Request): Promise<Response>;
|
|
56
|
+
setSubscriptionHandler(handler: SubscriptionRequestHandler): this;
|
|
57
|
+
}
|
|
58
|
+
//#endregion
|
|
59
|
+
export { LitePubRelay, MastodonRelay, Relay, RelayOptions };
|
package/dist/mod.js
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { createFederation, exportJwk, generateCryptoKeyPair, importJwk } from "@fedify/fedify";
|
|
2
|
+
import { Accept, Create, Delete, Follow, Move, Object as Object$1, Reject, Service, Undo, Update, isActor } from "@fedify/fedify/vocab";
|
|
3
|
+
|
|
4
|
+
//#region src/relay.ts
|
|
5
|
+
const RELAY_SERVER_ACTOR = "relay";
|
|
6
|
+
/**
|
|
7
|
+
* A Mastodon-compatible ActivityPub relay implementation.
|
|
8
|
+
* This relay follows Mastodon's relay protocol for maximum compatibility
|
|
9
|
+
* with Mastodon instances.
|
|
10
|
+
*
|
|
11
|
+
* @since 2.0.0
|
|
12
|
+
*/
|
|
13
|
+
var MastodonRelay = class {
|
|
14
|
+
#federation;
|
|
15
|
+
#options;
|
|
16
|
+
#subscriptionHandler;
|
|
17
|
+
constructor(options) {
|
|
18
|
+
this.#options = options;
|
|
19
|
+
this.#federation = options.federation ?? createFederation({
|
|
20
|
+
kv: options.kv,
|
|
21
|
+
queue: options.queue,
|
|
22
|
+
documentLoaderFactory: options.documentLoaderFactory,
|
|
23
|
+
authenticatedDocumentLoaderFactory: options.authenticatedDocumentLoaderFactory
|
|
24
|
+
});
|
|
25
|
+
this.#federation.setActorDispatcher("/users/{identifier}", async (ctx, identifier) => {
|
|
26
|
+
if (identifier !== RELAY_SERVER_ACTOR) return null;
|
|
27
|
+
const keys = await ctx.getActorKeyPairs(identifier);
|
|
28
|
+
return new Service({
|
|
29
|
+
id: ctx.getActorUri(identifier),
|
|
30
|
+
preferredUsername: identifier,
|
|
31
|
+
name: "ActivityPub Relay",
|
|
32
|
+
summary: "Mastodon-compatible ActivityPub relay server",
|
|
33
|
+
inbox: ctx.getInboxUri(),
|
|
34
|
+
followers: ctx.getFollowersUri(identifier),
|
|
35
|
+
url: ctx.getActorUri(identifier),
|
|
36
|
+
publicKey: keys[0].cryptographicKey,
|
|
37
|
+
assertionMethods: keys.map((k) => k.multikey)
|
|
38
|
+
});
|
|
39
|
+
}).setKeyPairsDispatcher(async (_ctx, identifier) => {
|
|
40
|
+
if (identifier !== RELAY_SERVER_ACTOR) return [];
|
|
41
|
+
const rsaPairJson = await options.kv.get([
|
|
42
|
+
"keypair",
|
|
43
|
+
"rsa",
|
|
44
|
+
identifier
|
|
45
|
+
]);
|
|
46
|
+
const ed25519PairJson = await options.kv.get([
|
|
47
|
+
"keypair",
|
|
48
|
+
"ed25519",
|
|
49
|
+
identifier
|
|
50
|
+
]);
|
|
51
|
+
if (rsaPairJson == null || ed25519PairJson == null) {
|
|
52
|
+
const rsaPair$1 = await generateCryptoKeyPair("RSASSA-PKCS1-v1_5");
|
|
53
|
+
const ed25519Pair$1 = await generateCryptoKeyPair("Ed25519");
|
|
54
|
+
await options.kv.set([
|
|
55
|
+
"keypair",
|
|
56
|
+
"rsa",
|
|
57
|
+
identifier
|
|
58
|
+
], {
|
|
59
|
+
privateKey: await exportJwk(rsaPair$1.privateKey),
|
|
60
|
+
publicKey: await exportJwk(rsaPair$1.publicKey)
|
|
61
|
+
});
|
|
62
|
+
await options.kv.set([
|
|
63
|
+
"keypair",
|
|
64
|
+
"ed25519",
|
|
65
|
+
identifier
|
|
66
|
+
], {
|
|
67
|
+
privateKey: await exportJwk(ed25519Pair$1.privateKey),
|
|
68
|
+
publicKey: await exportJwk(ed25519Pair$1.publicKey)
|
|
69
|
+
});
|
|
70
|
+
return [rsaPair$1, ed25519Pair$1];
|
|
71
|
+
}
|
|
72
|
+
const rsaPair = {
|
|
73
|
+
privateKey: await importJwk(rsaPairJson.privateKey, "private"),
|
|
74
|
+
publicKey: await importJwk(rsaPairJson.publicKey, "public")
|
|
75
|
+
};
|
|
76
|
+
const ed25519Pair = {
|
|
77
|
+
privateKey: await importJwk(ed25519PairJson.privateKey, "private"),
|
|
78
|
+
publicKey: await importJwk(ed25519PairJson.publicKey, "public")
|
|
79
|
+
};
|
|
80
|
+
return [rsaPair, ed25519Pair];
|
|
81
|
+
});
|
|
82
|
+
this.#federation.setFollowersDispatcher("/users/{identifier}/followers", async (_ctx, identifier) => {
|
|
83
|
+
if (identifier !== RELAY_SERVER_ACTOR) return null;
|
|
84
|
+
const activityIds = await options.kv.get(["followers"]) ?? [];
|
|
85
|
+
const actors = [];
|
|
86
|
+
for (const activityId of activityIds) {
|
|
87
|
+
const actorJson = await options.kv.get(["follower", activityId]);
|
|
88
|
+
const actor = await Object$1.fromJsonLd(actorJson);
|
|
89
|
+
if (!isActor(actor)) continue;
|
|
90
|
+
actors.push(actor);
|
|
91
|
+
}
|
|
92
|
+
return { items: actors };
|
|
93
|
+
});
|
|
94
|
+
this.#federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Follow, async (ctx, follow) => {
|
|
95
|
+
if (follow.id == null || follow.objectId == null) return;
|
|
96
|
+
const parsed = ctx.parseUri(follow.objectId);
|
|
97
|
+
const isPublicFollow = follow.objectId.href === "https://www.w3.org/ns/activitystreams#Public";
|
|
98
|
+
if (!isPublicFollow && parsed?.type !== "actor") return;
|
|
99
|
+
const relayActorUri = ctx.getActorUri(RELAY_SERVER_ACTOR);
|
|
100
|
+
const recipient = await follow.getActor(ctx);
|
|
101
|
+
if (recipient == null || recipient.id == null || recipient.preferredUsername == null || recipient.inboxId == null) return;
|
|
102
|
+
let approved = false;
|
|
103
|
+
if (this.#subscriptionHandler) approved = await this.#subscriptionHandler(ctx, recipient);
|
|
104
|
+
if (approved) {
|
|
105
|
+
const followers = await options.kv.get(["followers"]) ?? [];
|
|
106
|
+
followers.push(follow.id.href);
|
|
107
|
+
await options.kv.set(["followers"], followers);
|
|
108
|
+
await options.kv.set(["follower", follow.id.href], await recipient.toJsonLd());
|
|
109
|
+
await ctx.sendActivity({ identifier: RELAY_SERVER_ACTOR }, recipient, new Accept({
|
|
110
|
+
id: new URL(`#accepts`, relayActorUri),
|
|
111
|
+
actor: relayActorUri,
|
|
112
|
+
object: follow
|
|
113
|
+
}));
|
|
114
|
+
} else await ctx.sendActivity({ identifier: RELAY_SERVER_ACTOR }, recipient, new Reject({
|
|
115
|
+
id: new URL(`#rejects`, relayActorUri),
|
|
116
|
+
actor: relayActorUri,
|
|
117
|
+
object: follow
|
|
118
|
+
}));
|
|
119
|
+
}).on(Undo, async (ctx, undo) => {
|
|
120
|
+
const activity = await undo.getObject(ctx);
|
|
121
|
+
if (activity instanceof Follow) {
|
|
122
|
+
if (activity.id == null || activity.actorId == null) return;
|
|
123
|
+
const activityId = activity.id.href;
|
|
124
|
+
const followers = await options.kv.get(["followers"]) ?? [];
|
|
125
|
+
const updatedFollowers = followers.filter((id) => id !== activityId);
|
|
126
|
+
await options.kv.set(["followers"], updatedFollowers);
|
|
127
|
+
options.kv.delete(["follower", activityId]);
|
|
128
|
+
} else console.warn("Unsupported object type ({type}) for Undo activity: {object}", {
|
|
129
|
+
type: activity?.constructor.name,
|
|
130
|
+
object: activity
|
|
131
|
+
});
|
|
132
|
+
}).on(Create, async (ctx, create) => {
|
|
133
|
+
const sender = await create.getActor(ctx);
|
|
134
|
+
const excludeBaseUris = sender?.id ? [new URL(sender.id)] : [];
|
|
135
|
+
await ctx.forwardActivity({ identifier: RELAY_SERVER_ACTOR }, "followers", {
|
|
136
|
+
skipIfUnsigned: true,
|
|
137
|
+
excludeBaseUris,
|
|
138
|
+
preferSharedInbox: true
|
|
139
|
+
});
|
|
140
|
+
}).on(Delete, async (ctx, deleteActivity) => {
|
|
141
|
+
const sender = await deleteActivity.getActor(ctx);
|
|
142
|
+
const excludeBaseUris = sender?.id ? [new URL(sender.id)] : [];
|
|
143
|
+
await ctx.forwardActivity({ identifier: RELAY_SERVER_ACTOR }, "followers", {
|
|
144
|
+
skipIfUnsigned: true,
|
|
145
|
+
excludeBaseUris,
|
|
146
|
+
preferSharedInbox: true
|
|
147
|
+
});
|
|
148
|
+
}).on(Move, async (ctx, deleteActivity) => {
|
|
149
|
+
const sender = await deleteActivity.getActor(ctx);
|
|
150
|
+
const excludeBaseUris = sender?.id ? [new URL(sender.id)] : [];
|
|
151
|
+
await ctx.forwardActivity({ identifier: RELAY_SERVER_ACTOR }, "followers", {
|
|
152
|
+
skipIfUnsigned: true,
|
|
153
|
+
excludeBaseUris,
|
|
154
|
+
preferSharedInbox: true
|
|
155
|
+
});
|
|
156
|
+
}).on(Update, async (ctx, deleteActivity) => {
|
|
157
|
+
const sender = await deleteActivity.getActor(ctx);
|
|
158
|
+
const excludeBaseUris = sender?.id ? [new URL(sender.id)] : [];
|
|
159
|
+
await ctx.forwardActivity({ identifier: RELAY_SERVER_ACTOR }, "followers", {
|
|
160
|
+
skipIfUnsigned: true,
|
|
161
|
+
excludeBaseUris,
|
|
162
|
+
preferSharedInbox: true
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
get domain() {
|
|
167
|
+
return this.#options.domain || "localhost";
|
|
168
|
+
}
|
|
169
|
+
fetch(request) {
|
|
170
|
+
return this.#federation.fetch(request, { contextData: void 0 });
|
|
171
|
+
}
|
|
172
|
+
setSubscriptionHandler(handler) {
|
|
173
|
+
this.#subscriptionHandler = handler;
|
|
174
|
+
return this;
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
/**
|
|
178
|
+
* A LitePub-compatible ActivityPub relay implementation.
|
|
179
|
+
* This relay follows LitePub's relay protocol and extensions for
|
|
180
|
+
* enhanced federation capabilities.
|
|
181
|
+
*
|
|
182
|
+
* @since 2.0.0
|
|
183
|
+
*/
|
|
184
|
+
var LitePubRelay = class {
|
|
185
|
+
#federation;
|
|
186
|
+
#options;
|
|
187
|
+
#subscriptionHandler;
|
|
188
|
+
constructor(options) {
|
|
189
|
+
this.#options = options;
|
|
190
|
+
this.#federation = createFederation({ kv: options.kv });
|
|
191
|
+
}
|
|
192
|
+
get domain() {
|
|
193
|
+
return this.#options.domain || "localhost";
|
|
194
|
+
}
|
|
195
|
+
fetch(request) {
|
|
196
|
+
return this.#federation.fetch(request, { contextData: void 0 });
|
|
197
|
+
}
|
|
198
|
+
setSubscriptionHandler(handler) {
|
|
199
|
+
this.#subscriptionHandler = handler;
|
|
200
|
+
return this;
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
//#endregion
|
|
205
|
+
export { LitePubRelay, MastodonRelay };
|
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@fedify/relay",
|
|
3
|
+
"version": "2.0.0-pr.471.1",
|
|
4
|
+
"description": "ActivityPub relay support for Fedify",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"Fedify",
|
|
7
|
+
"ActivityPub",
|
|
8
|
+
"Fediverse",
|
|
9
|
+
"Relay"
|
|
10
|
+
],
|
|
11
|
+
"author": {
|
|
12
|
+
"name": "Jiwon Kwon",
|
|
13
|
+
"email": "jiwonkwon@duck.com"
|
|
14
|
+
},
|
|
15
|
+
"homepage": "https://fedify.dev/",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/fedify-dev/fedify.git",
|
|
19
|
+
"directory": "packages/relay"
|
|
20
|
+
},
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/fedify-dev/fedify/issues"
|
|
24
|
+
},
|
|
25
|
+
"funding": [
|
|
26
|
+
"https://opencollective.com/fedify",
|
|
27
|
+
"https://github.com/sponsors/dahlia"
|
|
28
|
+
],
|
|
29
|
+
"type": "module",
|
|
30
|
+
"main": "./dist/mod.cjs",
|
|
31
|
+
"module": "./dist/mod.js",
|
|
32
|
+
"types": "./dist/mod.d.ts",
|
|
33
|
+
"exports": {
|
|
34
|
+
".": {
|
|
35
|
+
"types": {
|
|
36
|
+
"import": "./dist/mod.d.ts",
|
|
37
|
+
"require": "./dist/mod.d.cts",
|
|
38
|
+
"default": "./dist/mod.d.ts"
|
|
39
|
+
},
|
|
40
|
+
"import": "./dist/mod.js",
|
|
41
|
+
"require": "./dist/mod.cjs",
|
|
42
|
+
"default": "./dist/mod.js"
|
|
43
|
+
},
|
|
44
|
+
"./package.json": "./package.json"
|
|
45
|
+
},
|
|
46
|
+
"files": [
|
|
47
|
+
"dist/",
|
|
48
|
+
"package.json"
|
|
49
|
+
],
|
|
50
|
+
"peerDependencies": {
|
|
51
|
+
"@fedify/fedify": "^2.0.0-pr.471.1"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"tsdown": "^0.12.9",
|
|
55
|
+
"typescript": "^5.9.3"
|
|
56
|
+
},
|
|
57
|
+
"scripts": {
|
|
58
|
+
"build": "deno task codegen && tsdown",
|
|
59
|
+
"prepublish": "deno task codegen && tsdown",
|
|
60
|
+
"test": "deno task codegen && tsdown && cd dist/ && node --test",
|
|
61
|
+
"test:bun": "deno task codegen && deno task pnpm:build-vocab && tsdown && cd dist/ && bun test --timeout 60000",
|
|
62
|
+
"test:cfworkers": "deno task codegen && wrangler deploy --dry-run --outdir src/cfworkers && node --import=tsx src/cfworkers/client.ts"
|
|
63
|
+
}
|
|
64
|
+
}
|