@fedify/fedify 2.5.0-dev.2609 → 2.5.0-dev.2618

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 (123) hide show
  1. package/dist/{builder-BLCvt0zp.mjs → builder-C-zhu1PC.mjs} +82 -3
  2. package/dist/compat/mod.d.cts +1 -1
  3. package/dist/compat/mod.d.ts +1 -1
  4. package/dist/compat/transformers.test.mjs +1 -1
  5. package/dist/{compound-proof-DNhLvscb.mjs → compound-proof-B6Mz3pzt.mjs} +2 -2
  6. package/dist/{context-ByflxsY9.d.cts → context-C1NqcIct.d.cts} +1061 -443
  7. package/dist/{context-uQUY5fQT.d.ts → context-xrqq4Hrq.d.ts} +1064 -446
  8. package/dist/{deno-DYkYmKp0.mjs → deno-CaPDSXDl.mjs} +1 -1
  9. package/dist/{docloader-B9RO5nh9.mjs → docloader-kYMkiXaB.mjs} +2 -2
  10. package/dist/federation/builder.test.mjs +1 -1
  11. package/dist/federation/compound-fanout.test.mjs +3 -3
  12. package/dist/federation/handler-compound-proof.test.mjs +3 -3
  13. package/dist/federation/handler.test.mjs +4 -4
  14. package/dist/federation/hashlink-media.test.mjs +2 -2
  15. package/dist/federation/idempotency.test.mjs +2 -2
  16. package/dist/federation/inbox-actorless.test.mjs +5 -5
  17. package/dist/federation/inbox-report.test.mjs +220 -5
  18. package/dist/federation/inbox-view.test.mjs +1 -1
  19. package/dist/federation/inbox.test.mjs +1 -1
  20. package/dist/federation/metrics.test.mjs +1 -1
  21. package/dist/federation/middleware.test.mjs +7 -7
  22. package/dist/federation/mod.cjs +1 -1
  23. package/dist/federation/mod.d.cts +3 -3
  24. package/dist/federation/mod.d.ts +4 -4
  25. package/dist/federation/mod.js +1 -1
  26. package/dist/federation/mq.test.mjs +1 -1
  27. package/dist/federation/outbound-body.test.d.mts +2 -0
  28. package/dist/federation/outbound-body.test.mjs +149 -0
  29. package/dist/federation/outbound-capture.test.d.mts +2 -0
  30. package/dist/federation/outbound-capture.test.mjs +394 -0
  31. package/dist/federation/outbound-dispatch.test.d.mts +2 -0
  32. package/dist/federation/outbound-dispatch.test.mjs +368 -0
  33. package/dist/federation/outbound-document.test.d.mts +2 -0
  34. package/dist/federation/outbound-document.test.mjs +171 -0
  35. package/dist/federation/outbound-provenance.test.d.mts +2 -0
  36. package/dist/federation/outbound-provenance.test.mjs +543 -0
  37. package/dist/federation/outbound-report.test.d.mts +2 -0
  38. package/dist/federation/outbound-report.test.mjs +5475 -0
  39. package/dist/federation/outbound-workers.test.d.mts +2 -0
  40. package/dist/federation/outbound-workers.test.mjs +64 -0
  41. package/dist/federation/outbound-workflow.test.d.mts +2 -0
  42. package/dist/federation/outbound-workflow.test.mjs +154 -0
  43. package/dist/federation/outgoing-proof.test.mjs +4 -4
  44. package/dist/federation/portable-actor.test.mjs +3 -3
  45. package/dist/federation/portable-chain.test.mjs +3 -3
  46. package/dist/federation/portable-collection.test.mjs +4 -4
  47. package/dist/federation/portable-delivery.test.mjs +106 -7
  48. package/dist/federation/portable-gateway-keys.test.mjs +5 -5
  49. package/dist/federation/portable-inbox.test.mjs +430 -5
  50. package/dist/federation/portable-parse-uri.test.mjs +1 -1
  51. package/dist/federation/portable-route-activity.test.mjs +2 -2
  52. package/dist/federation/portable.test.mjs +3 -3
  53. package/dist/federation/send.test.mjs +44 -3
  54. package/dist/federation/tasks/enqueue.test.mjs +1 -1
  55. package/dist/federation/tasks/tasks.test.mjs +2 -2
  56. package/dist/federation/temporal.test.mjs +1 -1
  57. package/dist/federation/webfinger.test.mjs +1 -1
  58. package/dist/{federation-DzMdu4zU.cjs → federation-c-ulWHXG.cjs} +2358 -405
  59. package/dist/{http-EoFzzh1s.js → http-C12QVNZt.js} +62 -32
  60. package/dist/{http-BdTJujR0.cjs → http-DwL_g670.cjs} +62 -32
  61. package/dist/{http-TgeyuTDM.mjs → http-NYqvoigN.mjs} +64 -34
  62. package/dist/{inbox-ChkbRxCN.mjs → inbox-CkL5YgHZ.mjs} +90 -3
  63. package/dist/{inbox-view-BosBZdlt.mjs → inbox-view-IJCZG0_b.mjs} +1 -1
  64. package/dist/{key-DbLNT-vA.d.ts → key-dwwAg5bA.d.ts} +1 -1
  65. package/dist/{kv-cache-CMgOYiLU.js → kv-cache-1BO1Deou.js} +1 -1
  66. package/dist/{kv-cache-CTA2i08a.mjs → kv-cache-BdFnnrOU.mjs} +1 -1
  67. package/dist/{kv-cache-B0o18_Ni.cjs → kv-cache-CcGUsFoZ.cjs} +1 -1
  68. package/dist/{ld-CZsMAfOw.mjs → ld-CEOS8Pho.mjs} +2 -2
  69. package/dist/{ld-Inagww4R.d.ts → ld-o4LnhCYY.d.ts} +2 -2
  70. package/dist/{metrics-Kc9mP1LD.mjs → metrics-CvuJeWaJ.mjs} +1 -1
  71. package/dist/{metrics-DoRtI0qU.d.ts → metrics-DVKYY6K1.d.ts} +1 -1
  72. package/dist/{middleware-C1coPz8l.mjs → middleware-BPZ9KhAd.mjs} +1 -1
  73. package/dist/{middleware-B-pjIvQ8.js → middleware-CzcR7pNr.js} +2303 -351
  74. package/dist/{middleware-CIRoIVSU.mjs → middleware-DMrpt2wD.mjs} +1573 -325
  75. package/dist/{mod-B7j30IQf.d.ts → mod-CQC9xlLZ.d.ts} +3 -3
  76. package/dist/{mod-B4kBw1-8.d.ts → mod-D22ZsiKi.d.ts} +2 -2
  77. package/dist/{mod-BN0TTfyQ.d.ts → mod-DO0epJGH.d.ts} +1 -1
  78. package/dist/{mod-DF2gbN_d.d.cts → mod-TwiQgEf3.d.cts} +1 -1
  79. package/dist/mod.cjs +4 -4
  80. package/dist/mod.d.cts +4 -4
  81. package/dist/mod.d.ts +8 -8
  82. package/dist/mod.js +4 -4
  83. package/dist/nodeinfo/handler.test.mjs +1 -1
  84. package/dist/outbound-body-CckPA5gc.mjs +83 -0
  85. package/dist/outbound-capture-DVgnRzlf.mjs +184 -0
  86. package/dist/outbound-document-BMgwLXTY.mjs +179 -0
  87. package/dist/{owner-Dwz4fGUW.mjs → owner-BOyHaAlU.mjs} +3 -3
  88. package/dist/{portable-collection-BikiijSk.js → portable-collection-B4HFXlgN.js} +2 -2
  89. package/dist/{portable-collection-Oxfavx8f.mjs → portable-collection-BBSMcaDY.mjs} +2 -2
  90. package/dist/{portable-collection-Ci6vIhbR.cjs → portable-collection-D_zZTK4C.cjs} +2 -2
  91. package/dist/{portable-key--fWpTNDN.mjs → portable-key-CF8xK_lj.mjs} +1 -1
  92. package/dist/{proof-D6Kb0Qq9.mjs → proof-VrE-GaGv.mjs} +3 -3
  93. package/dist/{send-BFti0L3I.mjs → send-CY6agn9T.mjs} +153 -40
  94. package/dist/sig/compound-proof-discovery.test.mjs +1 -1
  95. package/dist/sig/compound-proof-producer.test.mjs +1 -1
  96. package/dist/sig/compound-proof-verification.test.mjs +2 -2
  97. package/dist/sig/compound-proof.test.mjs +2 -2
  98. package/dist/sig/http.test.mjs +38 -2
  99. package/dist/sig/key.test.mjs +1 -1
  100. package/dist/sig/ld.test.mjs +1 -1
  101. package/dist/sig/mitra-receive-interop.test.mjs +4 -4
  102. package/dist/sig/mitra-send-interop.test.mjs +3 -3
  103. package/dist/sig/mod.cjs +2 -2
  104. package/dist/sig/mod.d.ts +3 -3
  105. package/dist/sig/mod.js +2 -2
  106. package/dist/sig/owner.test.mjs +1 -1
  107. package/dist/sig/portable-collection.test.mjs +1 -1
  108. package/dist/sig/portable-interop.test.mjs +3 -3
  109. package/dist/sig/portable-key.test.mjs +5 -5
  110. package/dist/sig/proof.test.mjs +3 -3
  111. package/dist/sig/tootik-request-interop.test.mjs +4 -4
  112. package/dist/sig/tootik-send-interop.test.mjs +3 -3
  113. package/dist/sig/verification.test.mjs +3 -3
  114. package/dist/{temporal-DlYL_raW.mjs → temporal-QO2pe6W4.mjs} +1 -1
  115. package/dist/testing/kv.test.mjs +1 -1
  116. package/dist/testing/mod.d.mts +1030 -412
  117. package/dist/utils/docloader.test.mjs +2 -2
  118. package/dist/utils/kv-cache.test.mjs +1 -1
  119. package/dist/utils/mod.cjs +1 -1
  120. package/dist/utils/mod.d.ts +1 -1
  121. package/dist/utils/mod.js +1 -1
  122. package/package.json +7 -7
  123. package/skills/fedify/SKILL.md +60 -1
@@ -0,0 +1,2 @@
1
+ import { Temporal } from "temporal-polyfill";
2
+ import { URLPattern } from "urlpattern-polyfill";
@@ -0,0 +1,64 @@
1
+ import "temporal-polyfill";
2
+ import "urlpattern-polyfill";
3
+ globalThis.addEventListener = () => {};
4
+ import { t as projectSenderPayload } from "../outbound-document-BMgwLXTY.mjs";
5
+ import { t as captureResponseBody } from "../outbound-capture-DVgnRzlf.mjs";
6
+ import { test } from "@fedify/fixture";
7
+ import { deepStrictEqual, ok, rejects, strictEqual } from "node:assert/strict";
8
+ //#region src/federation/outbound-workers.test.ts
9
+ test("outbound sender payload privacy on Workers", () => {
10
+ const secret = "https://private.example/recipient";
11
+ const input = {
12
+ "@context": "https://www.w3.org/ns/activitystreams",
13
+ type: "Create",
14
+ bcc: [secret],
15
+ object: {
16
+ "as:bto": [secret],
17
+ content: "visible"
18
+ }
19
+ };
20
+ const result = projectSenderPayload(input);
21
+ ok(result.type === "available");
22
+ strictEqual(result.redacted, true);
23
+ strictEqual(JSON.stringify(result.value).includes(secret), false);
24
+ ok(Object.isFrozen(result.value));
25
+ strictEqual(input.bcc[0], secret);
26
+ deepStrictEqual(projectSenderPayload({
27
+ ...input,
28
+ "@context": [input["@context"], { private: "as:bcc" }]
29
+ }), {
30
+ stage: "sender-payload",
31
+ type: "unavailable",
32
+ reason: "privacy-filter-unavailable"
33
+ });
34
+ });
35
+ test("outbound body capture revocation on Workers", async () => {
36
+ let cancelled = 0;
37
+ let reader;
38
+ let signal;
39
+ const source = new ReadableStream({
40
+ pull() {},
41
+ cancel() {
42
+ cancelled++;
43
+ return new Promise(() => {});
44
+ }
45
+ }, { highWaterMark: 0 });
46
+ deepStrictEqual(await captureResponseBody(new Response(source), {
47
+ maxBytes: 16,
48
+ timeoutMs: 10
49
+ }, async (body, current) => {
50
+ signal = current;
51
+ reader = body.getReader();
52
+ await rejects(reader.read());
53
+ }), {
54
+ type: "deadline",
55
+ bytesExposed: 0
56
+ });
57
+ ok(signal.aborted);
58
+ strictEqual(cancelled, 1);
59
+ strictEqual(source.locked, false);
60
+ await rejects(reader.read());
61
+ reader.releaseLock();
62
+ });
63
+ //#endregion
64
+ export {};
@@ -0,0 +1,2 @@
1
+ import { Temporal } from "temporal-polyfill";
2
+ import { URLPattern } from "urlpattern-polyfill";
@@ -0,0 +1,154 @@
1
+ import { Temporal } from "temporal-polyfill";
2
+ import "urlpattern-polyfill";
3
+ globalThis.addEventListener = () => {};
4
+ import { t as MemoryKvStore } from "../kv-DCHkaJ9-.mjs";
5
+ import { i as rsaPrivateKey2, s as rsaPublicKey2 } from "../keys-DIJNHW-Y.mjs";
6
+ import { o as createFederation } from "../middleware-DMrpt2wD.mjs";
7
+ import { Create, Person } from "@fedify/vocab";
8
+ import { test } from "@fedify/fixture";
9
+ import { deepStrictEqual, ok, strictEqual } from "node:assert/strict";
10
+ import { once } from "node:events";
11
+ import { createServer } from "node:http";
12
+ //#region src/federation/outbound-workflow.test.ts
13
+ const { Deno: deno } = globalThis;
14
+ test({
15
+ name: "outbound recorder archives real HTTP exchanges across a queued retry",
16
+ ignore: deno != null && (await deno.permissions.query({ name: "net" })).state !== "granted",
17
+ async fn() {
18
+ let accepting = false;
19
+ const received = [];
20
+ const receiverErrors = [];
21
+ const server = createServer((request, response) => {
22
+ (async () => {
23
+ let body = "";
24
+ for await (const chunk of request) body += chunk.toString();
25
+ const status = accepting ? 202 : 503;
26
+ received.push({
27
+ status,
28
+ activity: JSON.parse(body)
29
+ });
30
+ response.writeHead(status, {
31
+ "Content-Type": "text/plain",
32
+ "Connection": "close"
33
+ });
34
+ response.end(accepting ? "accepted" : "retry later");
35
+ })().catch((error) => {
36
+ receiverErrors.push(error);
37
+ response.writeHead(500);
38
+ response.end();
39
+ });
40
+ });
41
+ server.listen(0, "127.0.0.1");
42
+ await once(server, "listening");
43
+ const inbox = new URL(`http://127.0.0.1:${server.address().port}/inbox`);
44
+ const queued = [];
45
+ const attempts = [];
46
+ const exchanges = [];
47
+ const workers = [];
48
+ const archive = /* @__PURE__ */ new Map();
49
+ const federation = createFederation({
50
+ kv: new MemoryKvStore(),
51
+ allowPrivateAddress: true,
52
+ manuallyStartQueue: true,
53
+ circuitBreaker: false,
54
+ outboxRetryPolicy: () => Temporal.Duration.from({ milliseconds: 0 }),
55
+ queue: { outbox: {
56
+ enqueue(message) {
57
+ queued.push(message);
58
+ return Promise.resolve();
59
+ },
60
+ listen: () => Promise.resolve()
61
+ } }
62
+ });
63
+ federation.setOutboundObservers({
64
+ senderPayload: true,
65
+ responseBodyCapture: {
66
+ maxBytes: 4096,
67
+ timeoutMs: 1e3,
68
+ async onBody(_ctx, event) {
69
+ const key = `${event.attemptId}:${event.exchangeIndex}`;
70
+ const chunks = [];
71
+ archive.set(key, chunks);
72
+ const reader = event.body.getReader();
73
+ try {
74
+ while (true) {
75
+ const chunk = await reader.read();
76
+ if (chunk.done) break;
77
+ chunks.push(chunk.value.slice());
78
+ }
79
+ } finally {
80
+ reader.releaseLock();
81
+ }
82
+ }
83
+ },
84
+ onAttempt(_ctx, report) {
85
+ attempts.push(report);
86
+ },
87
+ onHttpExchange(_ctx, report) {
88
+ exchanges.push(report);
89
+ },
90
+ onWorker(_ctx, report) {
91
+ workers.push(report);
92
+ }
93
+ });
94
+ try {
95
+ await federation.createContext(new URL("https://example.com"), void 0).sendActivity({
96
+ keyId: rsaPublicKey2.id,
97
+ privateKey: rsaPrivateKey2
98
+ }, new Person({
99
+ id: new URL(inbox.origin + "/actor"),
100
+ inbox
101
+ }), new Create({
102
+ id: new URL("https://example.com/workflow/activity"),
103
+ actor: new URL("https://example.com/person2")
104
+ }), {
105
+ fanout: "skip",
106
+ dispatchId: "saved-workflow-dispatch"
107
+ });
108
+ strictEqual(received.length, 0);
109
+ strictEqual(queued.length, 1);
110
+ await federation.processQueuedTask(void 0, queued.shift());
111
+ strictEqual(attempts.length, 1);
112
+ strictEqual(attempts[0].outcome.type, "failed");
113
+ ok(attempts[0].outcome.response.type === "final");
114
+ strictEqual(attempts[0].outcome.response.metadata.status, 503);
115
+ strictEqual(queued.length, 1);
116
+ const retry = workers[0].actions.find((action) => action.type === "scheduled" && action.action === "retry");
117
+ ok(retry?.type === "scheduled");
118
+ strictEqual(retry.queue.queueAttempt, 1);
119
+ accepting = true;
120
+ await federation.processQueuedTask(void 0, queued.shift());
121
+ strictEqual(queued.length, 0);
122
+ strictEqual(attempts.length, 2);
123
+ strictEqual(attempts[1].outcome.type, "succeeded");
124
+ strictEqual(attempts[0].deliveryId, attempts[1].deliveryId);
125
+ ok(attempts[0].attemptId !== attempts[1].attemptId);
126
+ deepStrictEqual(attempts[0].broadcast, attempts[1].broadcast);
127
+ strictEqual(attempts[1].queue?.queueAttempt, 1);
128
+ for (const report of attempts) {
129
+ strictEqual(report.provenance.dispatchId, "saved-workflow-dispatch");
130
+ strictEqual(report.senderPayload.type, "available");
131
+ strictEqual(report.httpExchangeCount, exchanges.filter((exchange) => exchange.attemptId === report.attemptId).length);
132
+ }
133
+ strictEqual(received.length, exchanges.length);
134
+ strictEqual(archive.size, exchanges.length);
135
+ for (const [index, exchange] of exchanges.entries()) {
136
+ deepStrictEqual(exchange.bodyCapture, {
137
+ type: "complete",
138
+ bytesExposed: received[index].status === 503 ? 11 : 8
139
+ });
140
+ const chunks = archive.get(`${exchange.attemptId}:${exchange.exchangeIndex}`);
141
+ const decoder = new TextDecoder();
142
+ strictEqual(chunks.map((chunk) => decoder.decode(chunk)).join(""), received[index].status === 503 ? "retry later" : "accepted");
143
+ }
144
+ deepStrictEqual(workers.map((worker) => worker.attemptsStarted), [1, 1]);
145
+ deepStrictEqual(receiverErrors, []);
146
+ } finally {
147
+ const closed = new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
148
+ server.closeAllConnections();
149
+ await closed;
150
+ }
151
+ }
152
+ });
153
+ //#endregion
154
+ export {};
@@ -7,11 +7,11 @@ import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
7
7
  import "../std__assert-DvMDRPes.mjs";
8
8
  import { t as assertRejects } from "../assert_rejects-CTptdYck.mjs";
9
9
  import { t as assert } from "../assert-D1Eafmd_.mjs";
10
- import { s as detachSignature, x as exportJwk } from "../ld-CZsMAfOw.mjs";
11
- import { s as signObject } from "../proof-D6Kb0Qq9.mjs";
10
+ import { s as detachSignature, x as exportJwk } from "../ld-CEOS8Pho.mjs";
11
+ import { s as signObject } from "../proof-VrE-GaGv.mjs";
12
12
  import { i as rsaPrivateKey2, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2 } from "../keys-DIJNHW-Y.mjs";
13
- import { C as getEmbeddedFirstGateway, S as getCompactRootId, c as assertPortableActorActivity, n as FederationImpl, o as createFederation, w as warnCompatibleIdsInJson } from "../middleware-CIRoIVSU.mjs";
14
- import { o as verifyCompoundPortableObjectProofs } from "../compound-proof-DNhLvscb.mjs";
13
+ import { C as getEmbeddedFirstGateway, S as getCompactRootId, c as assertPortableActorActivity, n as FederationImpl, o as createFederation, w as warnCompatibleIdsInJson } from "../middleware-DMrpt2wD.mjs";
14
+ import { o as verifyCompoundPortableObjectProofs } from "../compound-proof-B6Mz3pzt.mjs";
15
15
  import { configure, reset } from "@logtape/logtape";
16
16
  import { Accept, Announce, Create, Follow, Note, Person } from "@fedify/vocab";
17
17
  import { exportDidKey, parseIri } from "@fedify/vocab-runtime";
@@ -8,10 +8,10 @@ import { t as assertInstanceOf } from "../assert_instance_of-mrXWWNY-.mjs";
8
8
  import { t as assertThrows } from "../assert_throws-BYITOvPO.mjs";
9
9
  import "../std__assert-DvMDRPes.mjs";
10
10
  import { t as assert } from "../assert-D1Eafmd_.mjs";
11
- import { d as verifyPortableObjectProof, s as signObject } from "../proof-D6Kb0Qq9.mjs";
12
- import { p as verifyRequest, u as signRequest } from "../http-TgeyuTDM.mjs";
11
+ import { d as verifyPortableObjectProof, s as signObject } from "../proof-VrE-GaGv.mjs";
12
+ import { p as verifyRequest, u as signRequest } from "../http-NYqvoigN.mjs";
13
13
  import { a as rsaPrivateKey3, c as rsaPublicKey3, i as rsaPrivateKey2, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2 } from "../keys-DIJNHW-Y.mjs";
14
- import { b as PORTABLE_OBJECT_CONTENT_TYPE, o as createFederation } from "../middleware-CIRoIVSU.mjs";
14
+ import { b as PORTABLE_OBJECT_CONTENT_TYPE, o as createFederation } from "../middleware-DMrpt2wD.mjs";
15
15
  import { n as RouterError } from "../router-u5O5KCQr.mjs";
16
16
  import { configure, reset } from "@logtape/logtape";
17
17
  import { Note, Person, Tombstone, lookupObject } from "@fedify/vocab";
@@ -7,10 +7,10 @@ import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
7
7
  import { t as assertInstanceOf } from "../assert_instance_of-mrXWWNY-.mjs";
8
8
  import "../std__assert-DvMDRPes.mjs";
9
9
  import { t as assert } from "../assert-D1Eafmd_.mjs";
10
- import { x as exportJwk } from "../ld-CZsMAfOw.mjs";
11
- import { d as verifyPortableObjectProof, l as verifyObject } from "../proof-D6Kb0Qq9.mjs";
10
+ import { x as exportJwk } from "../ld-CEOS8Pho.mjs";
11
+ import { d as verifyPortableObjectProof, l as verifyObject } from "../proof-VrE-GaGv.mjs";
12
12
  import { i as rsaPrivateKey2, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2 } from "../keys-DIJNHW-Y.mjs";
13
- import { n as FederationImpl } from "../middleware-CIRoIVSU.mjs";
13
+ import { n as FederationImpl } from "../middleware-DMrpt2wD.mjs";
14
14
  import { Create, DataIntegrityProof, Note, OrderedCollection, Person, traverseCollection } from "@fedify/vocab";
15
15
  import { FetchError, encodeMultibase, exportDidKey } from "@fedify/vocab-runtime";
16
16
  import { mockDocumentLoader, test } from "@fedify/fixture";
@@ -7,11 +7,11 @@ import { t as assertFalse } from "../assert_false-CPdRnc3E.mjs";
7
7
  import { t as assertThrows } from "../assert_throws-BYITOvPO.mjs";
8
8
  import "../std__assert-DvMDRPes.mjs";
9
9
  import { t as assert } from "../assert-D1Eafmd_.mjs";
10
- import { d as verifyPortableObjectProof, s as signObject } from "../proof-D6Kb0Qq9.mjs";
10
+ import { d as verifyPortableObjectProof, s as signObject } from "../proof-VrE-GaGv.mjs";
11
11
  import { n as ed25519PrivateKey, r as ed25519PublicKey } from "../keys-DIJNHW-Y.mjs";
12
- import { b as PORTABLE_OBJECT_CONTENT_TYPE, o as createFederation } from "../middleware-CIRoIVSU.mjs";
13
- import { t as verifyPortableObject } from "../portable-collection-Oxfavx8f.mjs";
14
- import { c as verifyServedPortableObjects } from "../compound-proof-DNhLvscb.mjs";
12
+ import { b as PORTABLE_OBJECT_CONTENT_TYPE, o as createFederation } from "../middleware-DMrpt2wD.mjs";
13
+ import { t as verifyPortableObject } from "../portable-collection-BBSMcaDY.mjs";
14
+ import { c as verifyServedPortableObjects } from "../compound-proof-B6Mz3pzt.mjs";
15
15
  import { n as RouterError } from "../router-u5O5KCQr.mjs";
16
16
  import { configure, reset } from "@logtape/logtape";
17
17
  import { Create, Note, Object as Object$1, OrderedCollection, PUBLIC_COLLECTION, Person, Tombstone, lookupObject, traverseCollection } from "@fedify/vocab";
@@ -4,12 +4,12 @@ globalThis.addEventListener = () => {};
4
4
  import { t as esm_default } from "../esm-BHXXx4Wx.mjs";
5
5
  import { t as MemoryKvStore } from "../kv-DCHkaJ9-.mjs";
6
6
  import { i as rsaPrivateKey2, s as rsaPublicKey2 } from "../keys-DIJNHW-Y.mjs";
7
- import { n as FederationImpl, r as InboxContextImpl } from "../middleware-CIRoIVSU.mjs";
8
- import { n as extractInboxes, t as SendActivityError } from "../send-BFti0L3I.mjs";
7
+ import { n as FederationImpl, r as InboxContextImpl, t as ContextImpl } from "../middleware-DMrpt2wD.mjs";
8
+ import { n as extractInboxes, t as SendActivityError } from "../send-CY6agn9T.mjs";
9
9
  import { Create, Endpoints, Note, Person } from "@fedify/vocab";
10
10
  import { parseIri } from "@fedify/vocab-runtime";
11
11
  import { mockDocumentLoader, test } from "@fedify/fixture";
12
- import { deepStrictEqual, ok, rejects, strictEqual } from "node:assert/strict";
12
+ import { deepStrictEqual, notStrictEqual, ok, rejects, strictEqual } from "node:assert/strict";
13
13
  //#region src/federation/portable-delivery.test.ts
14
14
  const DID = "did:key:z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2";
15
15
  const OTHER_DID = "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK";
@@ -344,6 +344,77 @@ test("Context.sendActivity() to a portable inbox", async (t) => {
344
344
  });
345
345
  esm_default.hardReset();
346
346
  });
347
+ test("portable fallback attempts retain one delivery across concrete gateway sends", async (t) => {
348
+ for (const operation of [
349
+ "send",
350
+ "inbox-forward",
351
+ "outbox-forward"
352
+ ]) for (const accepted of [true, false]) await t.step(`${operation}: ${accepted ? "accepted fallback" : "exhausted gateways"}`, async () => {
353
+ const raw = {
354
+ "@context": "https://www.w3.org/ns/activitystreams",
355
+ type: "Create",
356
+ id: "https://example.com/activities/forwarded",
357
+ actor: "https://example.com/person2",
358
+ extra: "preserve received payload"
359
+ };
360
+ const reports = [];
361
+ const deliveries = [];
362
+ const submitted = [];
363
+ esm_default.mockGlobal().post("begin:https://gw1.example/", (call) => {
364
+ submitted.push(JSON.parse(String(call.options.body)));
365
+ return 503;
366
+ }).post("begin:https://gw2.example/", (call) => {
367
+ submitted.push(JSON.parse(String(call.options.body)));
368
+ return accepted ? 202 : 502;
369
+ });
370
+ try {
371
+ const federation = new FederationImpl({
372
+ kv: new MemoryKvStore(),
373
+ allowPrivateAddress: true
374
+ });
375
+ federation.setOutboundObservers({
376
+ onDelivery(_ctx, event) {
377
+ deliveries.push(event);
378
+ },
379
+ onAttempt(_ctx, report) {
380
+ reports.push(report);
381
+ throw new Error("attempt observer cannot stop gateway fallback");
382
+ }
383
+ });
384
+ const ctx = federation.createContext(new URL("https://example.com/"));
385
+ ok(ctx instanceof ContextImpl);
386
+ const target = portableActor({ gateways: ["https://gw1.example", "https://gw2.example"] });
387
+ const pending = operation === "send" ? ctx.sendActivity(sender, target, createActivity(), { immediate: true }) : (operation === "inbox-forward" ? ctx.toInboxContext(null, raw, raw.id, Create.typeId.href) : ctx.toOutboxContext("alice", raw, raw.id, Create.typeId.href)).forwardActivity(sender, target, {
388
+ immediate: true,
389
+ skipIfUnsigned: false
390
+ });
391
+ if (accepted) await pending;
392
+ else await rejects(pending, (error) => error instanceof SendActivityError && error.statusCode === 502);
393
+ strictEqual(reports.length, 2);
394
+ deepStrictEqual(reports.map((report) => report.inboxUrl), [inboxOn("https://gw1.example"), inboxOn("https://gw2.example")]);
395
+ notStrictEqual(reports[0].attemptId, reports[1].attemptId);
396
+ strictEqual(reports[0].deliveryId, reports[1].deliveryId);
397
+ deepStrictEqual(reports[0].broadcast, reports[1].broadcast);
398
+ for (const report of reports) {
399
+ strictEqual(report.destinationInbox, inboxOn("https://gw1.example"));
400
+ strictEqual(report.portableInbox, PORTABLE_INBOX);
401
+ strictEqual(report.deliveryId, deliveries[0].deliveryId);
402
+ }
403
+ strictEqual(reports[0].outcome.type, "failed");
404
+ strictEqual(reports[1].outcome.type, accepted ? "succeeded" : "failed");
405
+ for (const [index, report] of reports.entries()) {
406
+ const response = report.outcome.response;
407
+ ok(response.type === "final");
408
+ strictEqual(response.metadata.status, index === 0 ? 503 : accepted ? 202 : 502);
409
+ strictEqual(report.httpExchangeCount, index === 1 && accepted ? 1 : 2);
410
+ strictEqual(response.exchangeIndex, report.httpExchangeCount - 1);
411
+ }
412
+ if (operation !== "send") for (const payload of submitted) deepStrictEqual(payload, raw);
413
+ } finally {
414
+ esm_default.hardReset();
415
+ }
416
+ });
417
+ });
347
418
  test("InboxContext.forwardActivity() to a portable inbox", async (t) => {
348
419
  esm_default.spyGlobal();
349
420
  const activity = {
@@ -374,25 +445,44 @@ test("InboxContext.forwardActivity() to a portable inbox", async (t) => {
374
445
  requests.push(call.url);
375
446
  return 202;
376
447
  });
377
- await createInboxContext(new FederationImpl({
448
+ const federation = new FederationImpl({
378
449
  kv: new MemoryKvStore(),
379
450
  allowPrivateAddress: true
380
- })).forwardActivity(sender, recipient, {
451
+ });
452
+ const events = [];
453
+ federation.setOutboundObservers({ onDelivery(_ctx, event) {
454
+ events.push(event);
455
+ } });
456
+ await createInboxContext(federation).forwardActivity(sender, recipient, {
381
457
  immediate: true,
382
458
  skipIfUnsigned: false
383
459
  });
384
460
  deepStrictEqual([...new Set(requests)], [inboxOn("https://gw1.example"), inboxOn("https://gw2.example")]);
461
+ deepStrictEqual(events.map((event) => event.stage), ["planned", "handoff"]);
462
+ strictEqual(events[0].deliveryId, events[1].deliveryId);
463
+ for (const event of events) {
464
+ strictEqual(event.destinationInbox, inboxOn("https://gw1.example"));
465
+ strictEqual(event.portableInbox, PORTABLE_INBOX);
466
+ }
467
+ const planned = events[0];
468
+ ok(planned.stage === "planned");
469
+ strictEqual(planned.mode, "portable-fallback");
385
470
  });
386
471
  await t.step("through the queue", async () => {
387
472
  esm_default.hardReset();
388
473
  esm_default.spyGlobal();
389
474
  const { queue, queued } = createQueue();
390
- await createInboxContext(new FederationImpl({
475
+ const federation = new FederationImpl({
391
476
  kv: new MemoryKvStore(),
392
477
  queue,
393
478
  manuallyStartQueue: true,
394
479
  allowPrivateAddress: true
395
- })).forwardActivity(sender, recipient, {
480
+ });
481
+ const events = [];
482
+ federation.setOutboundObservers({ onDelivery(_ctx, event) {
483
+ events.push(event);
484
+ } });
485
+ await createInboxContext(federation).forwardActivity(sender, recipient, {
396
486
  orderingKey: "key",
397
487
  skipIfUnsigned: false
398
488
  });
@@ -402,6 +492,15 @@ test("InboxContext.forwardActivity() to a portable inbox", async (t) => {
402
492
  strictEqual(message.portableInbox, PORTABLE_INBOX);
403
493
  deepStrictEqual(message.gatewayInboxes, [inboxOn("https://gw1.example"), inboxOn("https://gw2.example")]);
404
494
  strictEqual(queued[0].options?.orderingKey, `key\n${DID}`);
495
+ deepStrictEqual(events.map((event) => event.stage), ["planned", "handoff"]);
496
+ strictEqual(events[0].deliveryId, events[1].deliveryId);
497
+ for (const event of events) {
498
+ strictEqual(event.destinationInbox, inboxOn("https://gw1.example"));
499
+ strictEqual(event.portableInbox, PORTABLE_INBOX);
500
+ }
501
+ const planned = events[0];
502
+ ok(planned.stage === "planned");
503
+ strictEqual(planned.mode, "portable-fallback");
405
504
  });
406
505
  esm_default.hardReset();
407
506
  });
@@ -8,12 +8,12 @@ import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
8
8
  import "../std__assert-DvMDRPes.mjs";
9
9
  import { t as assertRejects } from "../assert_rejects-CTptdYck.mjs";
10
10
  import { r as getVerifiedKeyOwnerEvidence } from "../key-owner-evidence-APB6NpSk.mjs";
11
- import { m as signJsonLd } from "../ld-CZsMAfOw.mjs";
12
- import { m as verifyRequestDetailed, p as verifyRequest, u as signRequest } from "../http-TgeyuTDM.mjs";
11
+ import { m as signJsonLd } from "../ld-CEOS8Pho.mjs";
12
+ import { m as verifyRequestDetailed, p as verifyRequest, u as signRequest } from "../http-NYqvoigN.mjs";
13
13
  import { a as rsaPrivateKey3, i as rsaPrivateKey2, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2 } from "../keys-DIJNHW-Y.mjs";
14
- import { n as getAuthenticatedDocumentLoader } from "../docloader-B9RO5nh9.mjs";
15
- import { f as handleInbox, n as FederationImpl, o as createFederation } from "../middleware-CIRoIVSU.mjs";
16
- import { n as getKeyOwner, t as doesActorOwnKey } from "../owner-Dwz4fGUW.mjs";
14
+ import { n as getAuthenticatedDocumentLoader } from "../docloader-kYMkiXaB.mjs";
15
+ import { f as handleInbox, n as FederationImpl, o as createFederation } from "../middleware-DMrpt2wD.mjs";
16
+ import { n as getKeyOwner, t as doesActorOwnKey } from "../owner-BOyHaAlU.mjs";
17
17
  import { t as ActivityListenerSet } from "../activity-listener-CELFAodc.mjs";
18
18
  import { t as KvKeyCache } from "../keycache-N2opu4-3.mjs";
19
19
  import { Create, CryptographicKey, Person } from "@fedify/vocab";