@fedify/fedify 2.2.14-dev.2082 → 2.2.15-dev.2196
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{builder-Cwgo1nK5.mjs → builder-D8E6s6dI.mjs} +2 -2
- package/dist/{client-CQ7zd8tW.mjs → client-CJ4Y0keQ.mjs} +7 -3
- package/dist/compat/transformers.test.mjs +1 -1
- package/dist/{deno-CV4wsQzy.mjs → deno-qvNES9lw.mjs} +1 -1
- package/dist/docloader-DDSsh_Jq.mjs +79 -0
- package/dist/federation/builder.test.mjs +2 -2
- package/dist/federation/collection.test.mjs +1 -1
- package/dist/federation/handler.test.mjs +6 -6
- package/dist/federation/idempotency.test.mjs +3 -3
- package/dist/federation/kv.test.mjs +1 -1
- package/dist/federation/middleware.test.mjs +134 -7
- package/dist/federation/mod.cjs +1 -1
- package/dist/federation/mod.js +1 -1
- package/dist/federation/mq.test.mjs +98 -43
- package/dist/federation/negotiation.test.mjs +1 -1
- package/dist/federation/router.test.mjs +1 -1
- package/dist/federation/send.test.mjs +68 -5
- package/dist/federation/temporal.test.mjs +2 -2
- package/dist/federation/webfinger.test.mjs +2 -2
- package/dist/{http-C9MzFrOj.mjs → http-BW6AJOy7.mjs} +7 -3
- package/dist/{http-C5T-lWqB.js → http-CntX6ab0.js} +6 -2
- package/dist/{http-BWNuy1sb.cjs → http-RWKDbjsL.cjs} +6 -2
- package/dist/{key-DiziIx9e.mjs → key-Zl6BkQxS.mjs} +1 -1
- package/dist/{kv-cache-DGPEHc1T.cjs → kv-cache-C6Vsz04k.cjs} +34 -8
- package/dist/{kv-cache-CM-zJoep.js → kv-cache-CE9FWYpa.js} +35 -9
- package/dist/{ld-BAeuDKjx.mjs → ld-DMr3f6uX.mjs} +3 -3
- package/dist/{middleware-BbbKc-Tr.cjs → middleware-BR8TCS1H.cjs} +14 -5
- package/dist/{middleware-XNvq3NUZ.cjs → middleware-CSK_D6wm.cjs} +1 -1
- package/dist/{middleware-CkJOjZjS.mjs → middleware-CYTJ-y3g.mjs} +11 -11
- package/dist/{middleware-J32whqNP.js → middleware-Cu0bK3Iq.js} +13 -4
- package/dist/{middleware-DA2CvTQL.mjs → middleware-UrAjyLpb.mjs} +1 -1
- package/dist/{mod-DzPMtIV3.d.cts → mod-BYg1qMPB.d.cts} +2 -0
- package/dist/{mod-0MWo2Nu5.d.ts → mod-C9jqpInC.d.ts} +2 -0
- package/dist/mod.cjs +5 -5
- package/dist/mod.d.cts +1 -1
- package/dist/mod.d.ts +1 -1
- package/dist/mod.js +5 -5
- package/dist/nodeinfo/client.test.mjs +50 -2
- package/dist/nodeinfo/handler.test.mjs +2 -2
- package/dist/nodeinfo/mod.cjs +1 -1
- package/dist/nodeinfo/mod.js +1 -1
- package/dist/nodeinfo/types.test.mjs +1 -1
- package/dist/otel/exporter.test.mjs +1 -1
- package/dist/{owner-D08_JUSb.mjs → owner-CHo1EAg3.mjs} +2 -2
- package/dist/{proof-_S-wFUFM.mjs → proof-Blxp54sy.mjs} +2 -2
- package/dist/{proof-7T47tIu5.cjs → proof-CHNefbCg.cjs} +2 -2
- package/dist/{proof-BdRnLnh7.js → proof-D6abvk_O.js} +2 -2
- package/dist/{send-DmcKKipr.mjs → send-ConRtUh3.mjs} +12 -3
- package/dist/sig/http.test.mjs +43 -3
- package/dist/sig/key.test.mjs +2 -2
- package/dist/sig/ld.test.mjs +5 -2
- package/dist/sig/mod.cjs +2 -2
- package/dist/sig/mod.js +2 -2
- package/dist/sig/owner.test.mjs +2 -2
- package/dist/sig/proof.test.mjs +2 -2
- package/dist/{std__assert-CRDpx_HF.mjs → std__assert-BVFj8-_V.mjs} +1 -26
- package/dist/{temporal-CX_L28b-.mjs → temporal-BIaSZejA.mjs} +1 -1
- package/dist/{types-DWs-qZaY.cjs → types-DRPjmHVV.cjs} +7 -3
- package/dist/{types-sXITXcWa.js → types-miAYfiGO.js} +7 -3
- package/dist/utils/docloader.test.mjs +299 -3
- package/dist/utils/mod.cjs +1 -1
- package/dist/utils/mod.d.cts +1 -1
- package/dist/utils/mod.d.ts +1 -1
- package/dist/utils/mod.js +1 -1
- package/dist/vocab/cjs.test.mjs +1 -1
- package/package.json +7 -7
- package/dist/docloader-DV9F08Dn.mjs +0 -53
|
@@ -2,7 +2,7 @@ import "@js-temporal/polyfill";
|
|
|
2
2
|
import "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
4
|
import { n as RouterError, t as Router } from "./router-CrMLXoOr.mjs";
|
|
5
|
-
import { n as version, t as name } from "./deno-
|
|
5
|
+
import { n as version, t as name } from "./deno-qvNES9lw.mjs";
|
|
6
6
|
import { t as ActivityListenerSet } from "./activity-listener-IojJ9SVl.mjs";
|
|
7
7
|
import { Tombstone, getTypeId } from "@fedify/vocab";
|
|
8
8
|
import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
|
|
@@ -58,7 +58,7 @@ var FederationBuilderImpl = class {
|
|
|
58
58
|
this.collectionTypeIds = {};
|
|
59
59
|
}
|
|
60
60
|
async build(options) {
|
|
61
|
-
const { FederationImpl } = await import("./middleware-
|
|
61
|
+
const { FederationImpl } = await import("./middleware-UrAjyLpb.mjs");
|
|
62
62
|
const f = new FederationImpl(options);
|
|
63
63
|
const trailingSlashInsensitiveValue = f.router.trailingSlashInsensitive;
|
|
64
64
|
f.router = this.router.clone();
|
|
@@ -93,8 +93,8 @@ const DEFAULT_MAX_REDIRECTION = 5;
|
|
|
93
93
|
* bounce the request to an internal address.
|
|
94
94
|
* @param url The URL to fetch.
|
|
95
95
|
* @param headers The request headers.
|
|
96
|
-
* @returns The final response, or `null` if the URL is disallowed
|
|
97
|
-
* are too many redirects.
|
|
96
|
+
* @returns The final response, or `null` if the URL is disallowed, its
|
|
97
|
+
* hostname cannot be resolved, or there are too many redirects.
|
|
98
98
|
*/
|
|
99
99
|
async function fetchPublicUrl(url, headers) {
|
|
100
100
|
let current = url instanceof URL ? url : new URL(url);
|
|
@@ -104,7 +104,11 @@ async function fetchPublicUrl(url, headers) {
|
|
|
104
104
|
await validatePublicUrl(current.href);
|
|
105
105
|
} catch (e) {
|
|
106
106
|
if (e instanceof UrlError) {
|
|
107
|
-
logger.error("
|
|
107
|
+
if (e.reason === "dns") logger.error("DNS lookup failed for {url}: {error}", {
|
|
108
|
+
url: current.href,
|
|
109
|
+
error: e
|
|
110
|
+
});
|
|
111
|
+
else logger.error("Refused to fetch a private or invalid URL {url}: {error}", {
|
|
108
112
|
url: current.href,
|
|
109
113
|
error: e
|
|
110
114
|
});
|
|
@@ -4,7 +4,7 @@ globalThis.addEventListener = () => {};
|
|
|
4
4
|
import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
|
|
5
5
|
import { n as assertInstanceOf, t as assert } from "../assert-BhQd8QHP.mjs";
|
|
6
6
|
import { t as MemoryKvStore } from "../kv-CX8qfaad.mjs";
|
|
7
|
-
import { n as FederationImpl, v as actorDehydrator, y as autoIdAssigner } from "../middleware-
|
|
7
|
+
import { n as FederationImpl, v as actorDehydrator, y as autoIdAssigner } from "../middleware-CYTJ-y3g.mjs";
|
|
8
8
|
import { test } from "@fedify/fixture";
|
|
9
9
|
import { Follow, Person } from "@fedify/vocab";
|
|
10
10
|
//#region src/compat/transformers.test.ts
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import "@js-temporal/polyfill";
|
|
2
|
+
import "urlpattern-polyfill";
|
|
3
|
+
globalThis.addEventListener = () => {};
|
|
4
|
+
import { s as validateCryptoKey } from "./key-Zl6BkQxS.mjs";
|
|
5
|
+
import { n as doubleKnock } from "./http-BW6AJOy7.mjs";
|
|
6
|
+
import { curry } from "es-toolkit";
|
|
7
|
+
import { FetchError, UrlError, createActivityPubRequest, getRemoteDocument, logRequest, validatePublicUrl } from "@fedify/vocab-runtime";
|
|
8
|
+
import { getLogger } from "@logtape/logtape";
|
|
9
|
+
//#region src/utils/docloader.ts
|
|
10
|
+
const logger = getLogger([
|
|
11
|
+
"fedify",
|
|
12
|
+
"utils",
|
|
13
|
+
"docloader"
|
|
14
|
+
]);
|
|
15
|
+
const DEFAULT_MAX_REDIRECTION = 20;
|
|
16
|
+
/**
|
|
17
|
+
* Gets an authenticated {@link DocumentLoader} for the given identity.
|
|
18
|
+
* Note that an authenticated document loader intentionally does not cache
|
|
19
|
+
* the fetched documents.
|
|
20
|
+
* At most 20 HTTP redirects and alternate document links are followed in total
|
|
21
|
+
* per call. Revisiting a URL within that chain throws a {@link FetchError}.
|
|
22
|
+
* @param identity The identity to get the document loader for.
|
|
23
|
+
* The actor's key pair.
|
|
24
|
+
* @param options The options for the document loader.
|
|
25
|
+
* @returns The authenticated document loader.
|
|
26
|
+
* @throws {TypeError} If the key is invalid or unsupported.
|
|
27
|
+
* @since 0.4.0
|
|
28
|
+
*/
|
|
29
|
+
function getAuthenticatedDocumentLoader(identity, { allowPrivateAddress, maxRedirection, userAgent, specDeterminer, tracerProvider } = {}) {
|
|
30
|
+
validateCryptoKey(identity.privateKey);
|
|
31
|
+
async function load(url, options, redirected = 0, visited = /* @__PURE__ */ new Set()) {
|
|
32
|
+
options?.signal?.throwIfAborted();
|
|
33
|
+
let currentUrl = new URL(url).href;
|
|
34
|
+
await validateUrl(currentUrl);
|
|
35
|
+
visited.add(currentUrl);
|
|
36
|
+
const originalRequest = createActivityPubRequest(currentUrl, { userAgent });
|
|
37
|
+
function follow(nextUrl) {
|
|
38
|
+
options?.signal?.throwIfAborted();
|
|
39
|
+
if (redirected >= DEFAULT_MAX_REDIRECTION) throw new FetchError(currentUrl, `Too many redirections (${redirected + 1})`);
|
|
40
|
+
if (visited.has(nextUrl)) throw new FetchError(currentUrl, `Redirect loop detected: ${nextUrl}`);
|
|
41
|
+
redirected++;
|
|
42
|
+
}
|
|
43
|
+
const response = await doubleKnock(originalRequest, identity, {
|
|
44
|
+
maxRedirection,
|
|
45
|
+
specDeterminer,
|
|
46
|
+
log: curry(logRequest)(logger),
|
|
47
|
+
tracerProvider,
|
|
48
|
+
signal: options?.signal,
|
|
49
|
+
validateRedirect: async (nextUrl) => {
|
|
50
|
+
follow(nextUrl);
|
|
51
|
+
await validateUrl(nextUrl);
|
|
52
|
+
visited.add(nextUrl);
|
|
53
|
+
currentUrl = nextUrl;
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
return getRemoteDocument(currentUrl, response, (alternateUrl) => {
|
|
57
|
+
follow(alternateUrl);
|
|
58
|
+
return load(alternateUrl, options, redirected, visited);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
async function validateUrl(url) {
|
|
62
|
+
if (!allowPrivateAddress) try {
|
|
63
|
+
await validatePublicUrl(url);
|
|
64
|
+
} catch (error) {
|
|
65
|
+
if (error instanceof UrlError) if (error.reason === "dns") logger.debug("DNS lookup failed for {url}", {
|
|
66
|
+
url,
|
|
67
|
+
error
|
|
68
|
+
});
|
|
69
|
+
else logger.error("Disallowed private URL: {url}", {
|
|
70
|
+
url,
|
|
71
|
+
error
|
|
72
|
+
});
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return (url, options) => load(url, options);
|
|
77
|
+
}
|
|
78
|
+
//#endregion
|
|
79
|
+
export { getAuthenticatedDocumentLoader as t };
|
|
@@ -3,10 +3,10 @@ import "urlpattern-polyfill";
|
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
4
|
import { n as RouterError } from "../router-CrMLXoOr.mjs";
|
|
5
5
|
import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
|
|
6
|
-
import {
|
|
6
|
+
import { r as assertExists } from "../std__assert-BVFj8-_V.mjs";
|
|
7
7
|
import { t as assertThrows } from "../assert_throws-4NwKEy2q.mjs";
|
|
8
8
|
import { t as MemoryKvStore } from "../kv-CX8qfaad.mjs";
|
|
9
|
-
import { n as createFederationBuilder } from "../builder-
|
|
9
|
+
import { n as createFederationBuilder } from "../builder-D8E6s6dI.mjs";
|
|
10
10
|
import { test } from "@fedify/fixture";
|
|
11
11
|
import { Activity, Note, Person } from "@fedify/vocab";
|
|
12
12
|
//#region src/federation/builder.test.ts
|
|
@@ -2,7 +2,7 @@ import "@js-temporal/polyfill";
|
|
|
2
2
|
import "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
4
|
import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
|
|
5
|
-
import "../std__assert-
|
|
5
|
+
import "../std__assert-BVFj8-_V.mjs";
|
|
6
6
|
import { n as digest, t as buildCollectionSynchronizationHeader } from "../collection-DnoYNwBC.mjs";
|
|
7
7
|
import { test } from "@fedify/fixture";
|
|
8
8
|
import { decodeHex } from "byte-encodings/hex";
|
|
@@ -3,17 +3,17 @@ import "urlpattern-polyfill";
|
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
4
|
import { n as createOutboxContext, r as createRequestContext, t as createInboxContext } from "../context-Dk_tacqz.mjs";
|
|
5
5
|
import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
|
|
6
|
-
import "../std__assert-
|
|
6
|
+
import "../std__assert-BVFj8-_V.mjs";
|
|
7
7
|
import { t as assertRejects } from "../assert_rejects-B-qJtC9Z.mjs";
|
|
8
8
|
import { n as assertInstanceOf, t as assert } from "../assert-BhQd8QHP.mjs";
|
|
9
9
|
import { r as parseAcceptSignature } from "../accept-Dd__NiUL.mjs";
|
|
10
|
-
import { a as generateCryptoKeyPair } from "../key-
|
|
11
|
-
import { s as signRequest } from "../http-
|
|
10
|
+
import { a as generateCryptoKeyPair } from "../key-Zl6BkQxS.mjs";
|
|
11
|
+
import { s as signRequest } from "../http-BW6AJOy7.mjs";
|
|
12
12
|
import { a as rsaPrivateKey3, c as rsaPublicKey3, s as rsaPublicKey2 } from "../keys-BAK-tUlf.mjs";
|
|
13
|
-
import { a as compactJsonLd, p as signJsonLd } from "../ld-
|
|
14
|
-
import { r as signObject } from "../proof-
|
|
13
|
+
import { a as compactJsonLd, p as signJsonLd } from "../ld-DMr3f6uX.mjs";
|
|
14
|
+
import { r as signObject } from "../proof-Blxp54sy.mjs";
|
|
15
15
|
import { t as MemoryKvStore } from "../kv-CX8qfaad.mjs";
|
|
16
|
-
import { c as handleActor, d as handleInbox, f as handleObject, h as respondWithObjectIfAcceptable, l as handleCollection, m as respondWithObject, o as createFederation, p as handleOutbox, u as handleCustomCollection } from "../middleware-
|
|
16
|
+
import { c as handleActor, d as handleInbox, f as handleObject, h as respondWithObjectIfAcceptable, l as handleCollection, m as respondWithObject, o as createFederation, p as handleOutbox, u as handleCustomCollection } from "../middleware-CYTJ-y3g.mjs";
|
|
17
17
|
import { t as ActivityListenerSet } from "../activity-listener-IojJ9SVl.mjs";
|
|
18
18
|
import { createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
|
|
19
19
|
import { Activity, Create, CryptographicKey, Multikey, Note, Person, Tombstone } from "@fedify/vocab";
|
|
@@ -2,11 +2,11 @@ import "@js-temporal/polyfill";
|
|
|
2
2
|
import "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
4
|
import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
|
|
5
|
-
import "../std__assert-
|
|
5
|
+
import "../std__assert-BVFj8-_V.mjs";
|
|
6
6
|
import { n as ed25519PrivateKey, r as ed25519PublicKey, t as ed25519Multikey } from "../keys-BAK-tUlf.mjs";
|
|
7
|
-
import { r as signObject } from "../proof-
|
|
7
|
+
import { r as signObject } from "../proof-Blxp54sy.mjs";
|
|
8
8
|
import { t as MemoryKvStore } from "../kv-CX8qfaad.mjs";
|
|
9
|
-
import { o as createFederation } from "../middleware-
|
|
9
|
+
import { o as createFederation } from "../middleware-CYTJ-y3g.mjs";
|
|
10
10
|
import { mockDocumentLoader, test } from "@fedify/fixture";
|
|
11
11
|
import { Create, Follow, Person } from "@fedify/vocab";
|
|
12
12
|
//#region src/federation/idempotency.test.ts
|
|
@@ -2,7 +2,7 @@ import { Temporal } from "@js-temporal/polyfill";
|
|
|
2
2
|
import "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
4
|
import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
|
|
5
|
-
import "../std__assert-
|
|
5
|
+
import "../std__assert-BVFj8-_V.mjs";
|
|
6
6
|
import { t as MemoryKvStore } from "../kv-CX8qfaad.mjs";
|
|
7
7
|
import { test } from "@fedify/fixture";
|
|
8
8
|
//#region src/federation/kv.test.ts
|
|
@@ -4,25 +4,26 @@ globalThis.addEventListener = () => {};
|
|
|
4
4
|
import { t as esm_default } from "../esm-DVILvP5e.mjs";
|
|
5
5
|
import { n as RouterError } from "../router-CrMLXoOr.mjs";
|
|
6
6
|
import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
|
|
7
|
-
import "../std__assert-
|
|
7
|
+
import "../std__assert-BVFj8-_V.mjs";
|
|
8
8
|
import { n as assertFalse, t as assertRejects } from "../assert_rejects-B-qJtC9Z.mjs";
|
|
9
9
|
import { n as assertInstanceOf, t as assert } from "../assert-BhQd8QHP.mjs";
|
|
10
10
|
import { t as assertThrows } from "../assert_throws-4NwKEy2q.mjs";
|
|
11
11
|
import { t as assertNotEquals } from "../assert_not_equals--wG9hV7u.mjs";
|
|
12
12
|
import { t as assertStrictEquals } from "../assert_strict_equals-Dmjbg-bA.mjs";
|
|
13
|
-
import { l as verifyRequest, s as signRequest } from "../http-
|
|
13
|
+
import { l as verifyRequest, s as signRequest } from "../http-BW6AJOy7.mjs";
|
|
14
14
|
import { a as rsaPrivateKey3, c as rsaPublicKey3, i as rsaPrivateKey2, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2, t as ed25519Multikey } from "../keys-BAK-tUlf.mjs";
|
|
15
|
-
import { t as getAuthenticatedDocumentLoader } from "../docloader-
|
|
16
|
-
import { a as compactJsonLd, h as verifyJsonLd, p as signJsonLd, s as detachSignature } from "../ld-
|
|
17
|
-
import { t as doesActorOwnKey } from "../owner-
|
|
18
|
-
import { i as verifyObject, r as signObject } from "../proof-
|
|
15
|
+
import { t as getAuthenticatedDocumentLoader } from "../docloader-DDSsh_Jq.mjs";
|
|
16
|
+
import { a as compactJsonLd, h as verifyJsonLd, p as signJsonLd, s as detachSignature } from "../ld-DMr3f6uX.mjs";
|
|
17
|
+
import { t as doesActorOwnKey } from "../owner-CHo1EAg3.mjs";
|
|
18
|
+
import { i as verifyObject, r as signObject } from "../proof-Blxp54sy.mjs";
|
|
19
19
|
import { t as MemoryKvStore } from "../kv-CX8qfaad.mjs";
|
|
20
|
-
import { i as KvSpecDeterminer, n as FederationImpl, o as createFederation, r as InboxContextImpl, t as ContextImpl } from "../middleware-
|
|
20
|
+
import { i as KvSpecDeterminer, n as FederationImpl, o as createFederation, r as InboxContextImpl, t as ContextImpl } from "../middleware-CYTJ-y3g.mjs";
|
|
21
21
|
import { createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
|
|
22
22
|
import * as vocab from "@fedify/vocab";
|
|
23
23
|
import { Create, Offer, Person, getTypeId, lookupObject } from "@fedify/vocab";
|
|
24
24
|
import { FetchError, UrlError, getDocumentLoader } from "@fedify/vocab-runtime";
|
|
25
25
|
import { configure, reset } from "@logtape/logtape";
|
|
26
|
+
import dns from "node:dns/promises";
|
|
26
27
|
import serialize from "json-canon";
|
|
27
28
|
//#region ../fixture/src/fixtures/example.com/create.json
|
|
28
29
|
var id$2 = "https://example.com/create";
|
|
@@ -738,6 +739,31 @@ test({
|
|
|
738
739
|
esm_default.hardReset();
|
|
739
740
|
}
|
|
740
741
|
});
|
|
742
|
+
test("Federation.fetch() [rfc9421] multiple POST signatures", async (t) => {
|
|
743
|
+
for (const validSecond of [true, false]) await t.step(validSecond ? "valid second signature" : "both signatures invalid", async () => {
|
|
744
|
+
const inbox = [];
|
|
745
|
+
const federation = createFederation({
|
|
746
|
+
kv: new MemoryKvStore(),
|
|
747
|
+
documentLoaderFactory: () => mockDocumentLoader,
|
|
748
|
+
contextLoaderFactory: () => mockDocumentLoader
|
|
749
|
+
});
|
|
750
|
+
federation.setActorDispatcher("/users/{identifier}", (ctx, identifier) => new vocab.Person({ id: ctx.getActorUri(identifier) }));
|
|
751
|
+
federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(vocab.Create, (_ctx, activity) => {
|
|
752
|
+
inbox.push(activity.id.href);
|
|
753
|
+
});
|
|
754
|
+
const signed = await signRequest(new Request("https://example.com/inbox", {
|
|
755
|
+
method: "POST",
|
|
756
|
+
headers: { "Content-Type": "application/activity+json" },
|
|
757
|
+
body: JSON.stringify(create_default)
|
|
758
|
+
}), rsaPrivateKey2, rsaPublicKey2.id, { spec: "rfc9421" });
|
|
759
|
+
const input = signed.headers.get("Signature-Input");
|
|
760
|
+
const signature = signed.headers.get("Signature");
|
|
761
|
+
signed.headers.set("Signature-Input", `${input}, ${input.replace(/^sig1=/, "sig2=")}`);
|
|
762
|
+
signed.headers.set("Signature", `sig1=:AAAAAA==:, ${validSecond ? signature.replace(/^sig1=/, "sig2=") : "sig2=:AAAAAA==:"}`);
|
|
763
|
+
assertEquals((await federation.fetch(signed, { contextData: void 0 })).status, validSecond ? 202 : 401);
|
|
764
|
+
assertEquals(inbox, validSecond ? [id$2] : []);
|
|
765
|
+
});
|
|
766
|
+
});
|
|
741
767
|
test("Federation.fetch()", async (t) => {
|
|
742
768
|
esm_default.spyGlobal();
|
|
743
769
|
esm_default.get("https://example.com/key2", {
|
|
@@ -1973,6 +1999,7 @@ test("FederationImpl.processQueuedTask()", async (t) => {
|
|
|
1973
1999
|
const kv = new MemoryKvStore();
|
|
1974
2000
|
const queuedMessages = [];
|
|
1975
2001
|
const federation = new FederationImpl({
|
|
2002
|
+
allowPrivateAddress: true,
|
|
1976
2003
|
kv,
|
|
1977
2004
|
queue: {
|
|
1978
2005
|
nativeRetrial: true,
|
|
@@ -2035,6 +2062,7 @@ test("FederationImpl.processQueuedTask()", async (t) => {
|
|
|
2035
2062
|
const kv = new MemoryKvStore();
|
|
2036
2063
|
let queuedMessages = [];
|
|
2037
2064
|
const federation = new FederationImpl({
|
|
2065
|
+
allowPrivateAddress: true,
|
|
2038
2066
|
kv,
|
|
2039
2067
|
queue: {
|
|
2040
2068
|
enqueue(message, _options) {
|
|
@@ -3529,6 +3557,105 @@ test("FederationImpl.processQueuedTask()", async (t) => {
|
|
|
3529
3557
|
assertEquals(queuedMessages, []);
|
|
3530
3558
|
});
|
|
3531
3559
|
});
|
|
3560
|
+
test({
|
|
3561
|
+
name: "FederationImpl.processQueuedTask() retries DNS failures",
|
|
3562
|
+
ignore: "Deno" in globalThis && (await Deno.permissions.query({ name: "net" })).state !== "granted",
|
|
3563
|
+
async fn(t) {
|
|
3564
|
+
for (const redirected of [false, true]) await t.step(`redirected: ${redirected}`, async () => {
|
|
3565
|
+
const destination = "https://delivery.invalid/inbox";
|
|
3566
|
+
const publicInbox = "https://8.8.8.8/inbox";
|
|
3567
|
+
const queuedMessages = [];
|
|
3568
|
+
const delays = [];
|
|
3569
|
+
const errors = [];
|
|
3570
|
+
const activities = [];
|
|
3571
|
+
const attempts = [];
|
|
3572
|
+
const delay = Temporal.Duration.from({ seconds: 5 });
|
|
3573
|
+
const federation = new FederationImpl({
|
|
3574
|
+
kv: new MemoryKvStore(),
|
|
3575
|
+
queue: {
|
|
3576
|
+
enqueue(message, options) {
|
|
3577
|
+
queuedMessages.push(message);
|
|
3578
|
+
delays.push(options?.delay);
|
|
3579
|
+
return Promise.resolve();
|
|
3580
|
+
},
|
|
3581
|
+
listen() {
|
|
3582
|
+
return Promise.resolve();
|
|
3583
|
+
}
|
|
3584
|
+
},
|
|
3585
|
+
documentLoaderFactory: () => mockDocumentLoader,
|
|
3586
|
+
contextLoaderFactory: () => mockDocumentLoader,
|
|
3587
|
+
onOutboxError(error, activity) {
|
|
3588
|
+
errors.push(error);
|
|
3589
|
+
activities.push(activity);
|
|
3590
|
+
},
|
|
3591
|
+
outboxRetryPolicy(options) {
|
|
3592
|
+
attempts.push(options.attempts);
|
|
3593
|
+
return delay;
|
|
3594
|
+
}
|
|
3595
|
+
});
|
|
3596
|
+
const message = {
|
|
3597
|
+
type: "outbox",
|
|
3598
|
+
id: crypto.randomUUID(),
|
|
3599
|
+
baseUrl: "https://example.com",
|
|
3600
|
+
keys: [],
|
|
3601
|
+
activity: {
|
|
3602
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
3603
|
+
type: "Create",
|
|
3604
|
+
id: "https://example.com/activities/dns-failure",
|
|
3605
|
+
actor: "https://example.com/users/alice",
|
|
3606
|
+
object: {
|
|
3607
|
+
type: "Note",
|
|
3608
|
+
content: "test"
|
|
3609
|
+
}
|
|
3610
|
+
},
|
|
3611
|
+
activityType: "https://www.w3.org/ns/activitystreams#Create",
|
|
3612
|
+
inbox: redirected ? publicInbox : destination,
|
|
3613
|
+
sharedInbox: false,
|
|
3614
|
+
started: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3615
|
+
attempt: 0,
|
|
3616
|
+
headers: {},
|
|
3617
|
+
traceContext: {}
|
|
3618
|
+
};
|
|
3619
|
+
const originalLookup = dns.lookup;
|
|
3620
|
+
const resolverError = /* @__PURE__ */ new Error("Resolver unavailable");
|
|
3621
|
+
const lookups = [];
|
|
3622
|
+
dns.lookup = ((hostname) => {
|
|
3623
|
+
lookups.push(hostname);
|
|
3624
|
+
return Promise.reject(resolverError);
|
|
3625
|
+
});
|
|
3626
|
+
try {
|
|
3627
|
+
esm_default.mockGlobal().catch(202);
|
|
3628
|
+
if (redirected) esm_default.route(publicInbox, {
|
|
3629
|
+
status: 307,
|
|
3630
|
+
headers: { Location: destination }
|
|
3631
|
+
});
|
|
3632
|
+
await federation.processQueuedTask(void 0, message);
|
|
3633
|
+
assertEquals(errors.length, 1);
|
|
3634
|
+
const error = errors[0];
|
|
3635
|
+
assertInstanceOf(error, FetchError);
|
|
3636
|
+
assertEquals(error.url.href, destination);
|
|
3637
|
+
assertInstanceOf(error.cause, UrlError);
|
|
3638
|
+
assertEquals(error.cause.reason, "dns");
|
|
3639
|
+
assertStrictEquals(error.cause.cause, resolverError);
|
|
3640
|
+
assertEquals(activities.length, 1);
|
|
3641
|
+
assertInstanceOf(activities[0], Create);
|
|
3642
|
+
assertEquals(activities[0].id?.href, "https://example.com/activities/dns-failure");
|
|
3643
|
+
assertEquals(attempts, [0]);
|
|
3644
|
+
assertEquals(queuedMessages, [{
|
|
3645
|
+
...message,
|
|
3646
|
+
attempt: 1
|
|
3647
|
+
}]);
|
|
3648
|
+
assertEquals(delays, [delay]);
|
|
3649
|
+
assertEquals(lookups, ["delivery.invalid"]);
|
|
3650
|
+
assertEquals(esm_default.callHistory.calls(destination).length, 0);
|
|
3651
|
+
assertEquals(esm_default.callHistory.calls().length, redirected ? 1 : 0);
|
|
3652
|
+
} finally {
|
|
3653
|
+
dns.lookup = originalLookup;
|
|
3654
|
+
esm_default.hardReset();
|
|
3655
|
+
}
|
|
3656
|
+
});
|
|
3657
|
+
}
|
|
3658
|
+
});
|
|
3532
3659
|
test("FederationImpl.processQueuedTask() permanent failure", async (t) => {
|
|
3533
3660
|
esm_default.spyGlobal();
|
|
3534
3661
|
esm_default.post("https://gone.example/inbox", {
|
package/dist/federation/mod.cjs
CHANGED
|
@@ -2,7 +2,7 @@ const { Temporal } = require("@js-temporal/polyfill");
|
|
|
2
2
|
const { URLPattern } = require("urlpattern-polyfill");
|
|
3
3
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
4
4
|
require("../chunk-DDcVe30Y.cjs");
|
|
5
|
-
const require_middleware = require("../middleware-
|
|
5
|
+
const require_middleware = require("../middleware-BR8TCS1H.cjs");
|
|
6
6
|
let es_toolkit = require("es-toolkit");
|
|
7
7
|
//#region src/federation/kv.ts
|
|
8
8
|
/**
|
package/dist/federation/mod.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Temporal } from "@js-temporal/polyfill";
|
|
2
2
|
import { URLPattern } from "urlpattern-polyfill";
|
|
3
|
-
import { a as createExponentialBackoffPolicy, c as buildCollectionSynchronizationHeader, d as Router, f as RouterError, i as SendActivityError, l as digest, o as respondWithObject, r as handleWebFinger, s as respondWithObjectIfAcceptable, t as createFederation, u as createFederationBuilder } from "../middleware-
|
|
3
|
+
import { a as createExponentialBackoffPolicy, c as buildCollectionSynchronizationHeader, d as Router, f as RouterError, i as SendActivityError, l as digest, o as respondWithObject, r as handleWebFinger, s as respondWithObjectIfAcceptable, t as createFederation, u as createFederationBuilder } from "../middleware-Cu0bK3Iq.js";
|
|
4
4
|
import { isEqual } from "es-toolkit";
|
|
5
5
|
//#region src/federation/kv.ts
|
|
6
6
|
/**
|
|
@@ -2,7 +2,7 @@ import { Temporal } from "@js-temporal/polyfill";
|
|
|
2
2
|
import "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
4
|
import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
|
|
5
|
-
import { n as
|
|
5
|
+
import { n as assertGreaterOrEqual } from "../std__assert-BVFj8-_V.mjs";
|
|
6
6
|
import { n as assertFalse } from "../assert_rejects-B-qJtC9Z.mjs";
|
|
7
7
|
import { t as assert } from "../assert-BhQd8QHP.mjs";
|
|
8
8
|
import { test } from "@fedify/fixture";
|
|
@@ -261,50 +261,105 @@ test("InProcessMessageQueue", async (t) => {
|
|
|
261
261
|
const listening = mq.listen((message) => {
|
|
262
262
|
messages.push(message);
|
|
263
263
|
}, controller);
|
|
264
|
-
|
|
265
|
-
await
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
264
|
+
try {
|
|
265
|
+
await t.step("enqueue()", async () => {
|
|
266
|
+
await mq.enqueue("Hello, world!");
|
|
267
|
+
});
|
|
268
|
+
await waitFor(() => messages.length > 0, 15e3);
|
|
269
|
+
await t.step("listen()", () => {
|
|
270
|
+
assertEquals(messages, ["Hello, world!"]);
|
|
271
|
+
});
|
|
272
|
+
await t.step("enqueue() with delay", async () => {
|
|
273
|
+
const { timers, enqueued } = captureTimers(() => mq.enqueue("Delayed message", { delay: Temporal.Duration.from({ seconds: 3 }) }));
|
|
274
|
+
await enqueued;
|
|
275
|
+
assertEquals(timers.length, 1);
|
|
276
|
+
assertEquals(timers[0].delay, 3e3);
|
|
277
|
+
await mq.enqueue("Single delay marker");
|
|
278
|
+
await waitFor(() => messages.includes("Single delay marker"), 15e3);
|
|
279
|
+
assertEquals(messages, ["Hello, world!", "Single delay marker"]);
|
|
280
|
+
timers[0].callback();
|
|
281
|
+
});
|
|
282
|
+
await waitFor(() => messages.length >= 3, 15e3);
|
|
283
|
+
await t.step("listen() with delay", () => {
|
|
284
|
+
assertEquals(messages, [
|
|
285
|
+
"Hello, world!",
|
|
286
|
+
"Single delay marker",
|
|
287
|
+
"Delayed message"
|
|
288
|
+
]);
|
|
289
|
+
});
|
|
290
|
+
while (messages.length > 0) messages.pop();
|
|
291
|
+
await t.step("enqueueMany()", async () => {
|
|
292
|
+
const testMessages = Array.from({ length: 5 }, (_, i) => `Batch message ${i}!`);
|
|
293
|
+
await mq.enqueueMany(testMessages);
|
|
294
|
+
});
|
|
295
|
+
await waitFor(() => messages.length >= 5, 15e3);
|
|
296
|
+
await t.step("listen() [multiple]", () => {
|
|
297
|
+
assertEquals(messages.length, 5);
|
|
298
|
+
for (let i = 0; i < 5; i++) assertEquals(messages[i], `Batch message ${i}!`);
|
|
299
|
+
});
|
|
300
|
+
while (messages.length > 0) messages.pop();
|
|
301
|
+
await t.step("enqueueMany() with delay", async () => {
|
|
302
|
+
const testMessages = Array.from({ length: 3 }, (_, i) => `Delayed batch ${i}!`);
|
|
303
|
+
const { timers, enqueued } = captureTimers(() => mq.enqueueMany(testMessages, { delay: Temporal.Duration.from({ seconds: 2 }) }));
|
|
304
|
+
await enqueued;
|
|
305
|
+
assertEquals(timers.length, 1);
|
|
306
|
+
assertEquals(timers[0].delay, 2e3);
|
|
307
|
+
await mq.enqueue("Batch delay marker");
|
|
308
|
+
await waitFor(() => messages.includes("Batch delay marker"), 15e3);
|
|
309
|
+
assertEquals(messages, ["Batch delay marker"]);
|
|
310
|
+
timers[0].callback();
|
|
311
|
+
});
|
|
312
|
+
await waitFor(() => messages.length >= 4, 15e3);
|
|
313
|
+
await t.step("listen() [delayed multiple]", () => {
|
|
314
|
+
assertEquals(messages.length, 4);
|
|
315
|
+
assertEquals(messages[0], "Batch delay marker");
|
|
316
|
+
for (let i = 0; i < 3; i++) assertEquals(messages[i + 1], `Delayed batch ${i}!`);
|
|
317
|
+
});
|
|
318
|
+
} finally {
|
|
319
|
+
controller.abort();
|
|
320
|
+
await listening;
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
function captureTimers(enqueue) {
|
|
324
|
+
const timers = [];
|
|
325
|
+
const original = globalThis.setTimeout;
|
|
326
|
+
globalThis.setTimeout = ((callback, delay) => {
|
|
327
|
+
timers.push({
|
|
328
|
+
callback,
|
|
329
|
+
delay
|
|
330
|
+
});
|
|
331
|
+
return 0;
|
|
299
332
|
});
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
333
|
+
try {
|
|
334
|
+
return {
|
|
335
|
+
timers,
|
|
336
|
+
enqueued: enqueue()
|
|
337
|
+
};
|
|
338
|
+
} finally {
|
|
339
|
+
globalThis.setTimeout = original;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
test("InProcessMessageQueue real delay", async (t) => {
|
|
343
|
+
for (const batch of [false, true]) await t.step(batch ? "enqueueMany()" : "enqueue()", async () => {
|
|
344
|
+
const mq = new InProcessMessageQueue();
|
|
345
|
+
const messages = [];
|
|
346
|
+
const controller = new AbortController();
|
|
347
|
+
const listening = mq.listen((message) => {
|
|
348
|
+
messages.push(message);
|
|
349
|
+
}, controller);
|
|
350
|
+
try {
|
|
351
|
+
const delayed = batch ? ["Delayed 1", "Delayed 2"] : ["Delayed"];
|
|
352
|
+
const options = { delay: Temporal.Duration.from({ milliseconds: 100 }) };
|
|
353
|
+
if (batch) await mq.enqueueMany(delayed, options);
|
|
354
|
+
else await mq.enqueue(delayed[0], options);
|
|
355
|
+
await mq.enqueue("Immediate");
|
|
356
|
+
await waitFor(() => messages.length >= delayed.length + 1, 15e3);
|
|
357
|
+
assertEquals(messages, ["Immediate", ...delayed]);
|
|
358
|
+
} finally {
|
|
359
|
+
controller.abort();
|
|
360
|
+
await listening;
|
|
361
|
+
}
|
|
305
362
|
});
|
|
306
|
-
controller.abort();
|
|
307
|
-
await listening;
|
|
308
363
|
});
|
|
309
364
|
test("InProcessMessageQueue orderingKey", async (t) => {
|
|
310
365
|
const mq = new InProcessMessageQueue();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import "@js-temporal/polyfill";
|
|
2
2
|
import "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
|
-
import "../std__assert-
|
|
4
|
+
import "../std__assert-BVFj8-_V.mjs";
|
|
5
5
|
import { n as assertFalse } from "../assert_rejects-B-qJtC9Z.mjs";
|
|
6
6
|
import { t as assert } from "../assert-BhQd8QHP.mjs";
|
|
7
7
|
import { t as acceptsJsonLd } from "../negotiation-BHtZN6Y1.mjs";
|
|
@@ -3,7 +3,7 @@ import "urlpattern-polyfill";
|
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
4
|
import { n as RouterError, t as Router } from "../router-CrMLXoOr.mjs";
|
|
5
5
|
import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
|
|
6
|
-
import "../std__assert-
|
|
6
|
+
import "../std__assert-BVFj8-_V.mjs";
|
|
7
7
|
import { n as assertFalse } from "../assert_rejects-B-qJtC9Z.mjs";
|
|
8
8
|
import { t as assert } from "../assert-BhQd8QHP.mjs";
|
|
9
9
|
import { t as assertThrows } from "../assert_throws-4NwKEy2q.mjs";
|