@fedify/fedify 2.4.0-dev.2157 → 2.4.0-dev.2164

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 (98) hide show
  1. package/dist/{builder-CLaS4-yA.mjs → builder-BqRmnx7p.mjs} +2 -2
  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-D7K94KPz.mjs → compound-proof-CxDBdtNz.mjs} +2 -2
  6. package/dist/{context-exYu7gDU.d.ts → context-BykNUf04.d.ts} +120 -3
  7. package/dist/{context-BMggDR-l.d.cts → context-L0Fnib9j.d.cts} +120 -3
  8. package/dist/{deno-D1gcWWqB.mjs → deno-CizGaYeG.mjs} +1 -1
  9. package/dist/{docloader-BMqr3hji.mjs → docloader-BBFAItNB.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 +5 -5
  14. package/dist/federation/hashlink-media.test.mjs +2 -2
  15. package/dist/federation/idempotency.test.mjs +2 -2
  16. package/dist/federation/keycache.test.mjs +67 -1
  17. package/dist/federation/metrics.test.mjs +1 -1
  18. package/dist/federation/middleware.test.mjs +7 -7
  19. package/dist/federation/mod.cjs +1 -1
  20. package/dist/federation/mod.d.cts +3 -3
  21. package/dist/federation/mod.d.ts +3 -3
  22. package/dist/federation/mod.js +1 -1
  23. package/dist/federation/outgoing-proof.test.mjs +5 -5
  24. package/dist/federation/portable-actor.test.mjs +3 -3
  25. package/dist/federation/portable-chain.test.mjs +3 -3
  26. package/dist/federation/portable-collection.test.mjs +4 -4
  27. package/dist/federation/portable-gateway-keys.test.mjs +180 -6
  28. package/dist/federation/portable-inbox.test.mjs +4 -4
  29. package/dist/federation/portable-parse-uri.test.d.mts +2 -0
  30. package/dist/federation/portable-parse-uri.test.mjs +348 -0
  31. package/dist/federation/portable.test.mjs +3 -3
  32. package/dist/federation/send.test.mjs +3 -3
  33. package/dist/federation/tasks/enqueue.test.mjs +1 -1
  34. package/dist/federation/tasks/tasks.test.mjs +2 -2
  35. package/dist/federation/temporal.test.mjs +1 -1
  36. package/dist/federation/webfinger.test.mjs +1 -1
  37. package/dist/{federation-2e8XrN7q.cjs → federation-DFCnp94y.cjs} +103 -29
  38. package/dist/{http-Dgtt45-K.js → http-Baovr6xq.js} +161 -7
  39. package/dist/{http-CjezPjFX.d.cts → http-Cok5-vIC.d.ts} +6 -1
  40. package/dist/{http-C-x_OLSp.d.ts → http-DC1cqAQ2.d.cts} +6 -1
  41. package/dist/{http-CGcLv8rh.mjs → http-DRH0n8TD.mjs} +7 -5
  42. package/dist/{http-BObQoB0j.cjs → http-R4g1McwE.cjs} +185 -11
  43. package/dist/{key-CPM4r6tP.mjs → key-D5awqp7u.mjs} +58 -7
  44. package/dist/key-owner-evidence-Bo3yaeL5.mjs +108 -0
  45. package/dist/{keycache-BInaECGj.mjs → keycache-PiOM9pM0.mjs} +46 -21
  46. package/dist/{kv-cache-bdPqTR1k.mjs → kv-cache-C7RWL7hz.mjs} +1 -1
  47. package/dist/{kv-cache-BweyIZTb.js → kv-cache-CZiBDLPi.js} +1 -1
  48. package/dist/{kv-cache-D2fnB8qu.cjs → kv-cache-Ct-KWRta.cjs} +1 -1
  49. package/dist/{ld-lU_ZqzsQ.mjs → ld-CyliEU48.mjs} +3 -3
  50. package/dist/{metrics-B41pRPfd.mjs → metrics-CFBEehTc.mjs} +1 -1
  51. package/dist/{middleware-xdukzV3x.mjs → middleware-B99JjvFn.mjs} +70 -20
  52. package/dist/{middleware-TrCVULgs.mjs → middleware-BmqEWsWP.mjs} +1 -1
  53. package/dist/{middleware-BYWyYjmX.js → middleware-CdpTFsa2.js} +103 -29
  54. package/dist/{mod-Bz9tFXI2.d.ts → mod--jv6vyrP.d.ts} +1 -1
  55. package/dist/{mod-DaiBrWDW.d.ts → mod-B5z_q9TH.d.ts} +1 -1
  56. package/dist/{mod-Mt4aO3DX.d.cts → mod-BA_F2x53.d.cts} +1 -1
  57. package/dist/{mod-Decmur1U.d.cts → mod-BLOXGs6m.d.cts} +1 -1
  58. package/dist/{mod-S--JxYjo.d.cts → mod-CPPqkyV_.d.cts} +1 -1
  59. package/dist/{mod-CnGtADvO.d.ts → mod-DK0y9fd-.d.ts} +1 -1
  60. package/dist/mod.cjs +4 -4
  61. package/dist/mod.d.cts +7 -7
  62. package/dist/mod.d.ts +7 -7
  63. package/dist/mod.js +4 -4
  64. package/dist/nodeinfo/handler.test.mjs +1 -1
  65. package/dist/{owner-D63yPo_i.mjs → owner-DKdtsrEq.mjs} +18 -3
  66. package/dist/{portable-collection-_lZg-osX.mjs → portable-collection-Cb5cdPIU.mjs} +2 -2
  67. package/dist/{portable-collection-B8ojoQ50.cjs → portable-collection-UajqDrSL.cjs} +15 -1
  68. package/dist/{portable-collection-C3xEzYri.js → portable-collection-uv4CuQXm.js} +15 -1
  69. package/dist/{portable-key-D8uWZF7b.mjs → portable-key-Bv1RdQ0n.mjs} +2 -1
  70. package/dist/{proof-C2YwD8a_.mjs → proof-CRTPWbVh.mjs} +4 -4
  71. package/dist/{send-D9SGhxTB.mjs → send-2dULlZc8.mjs} +3 -3
  72. package/dist/sig/compound-proof-discovery.test.mjs +1 -1
  73. package/dist/sig/compound-proof-producer.test.mjs +1 -1
  74. package/dist/sig/compound-proof-verification.test.mjs +2 -2
  75. package/dist/sig/compound-proof.test.mjs +2 -2
  76. package/dist/sig/http.test.mjs +2 -2
  77. package/dist/sig/key-owner-evidence.test.d.mts +2 -0
  78. package/dist/sig/key-owner-evidence.test.mjs +100 -0
  79. package/dist/sig/key.test.mjs +1 -1
  80. package/dist/sig/ld.test.mjs +2 -2
  81. package/dist/sig/mod.cjs +2 -2
  82. package/dist/sig/mod.d.cts +2 -2
  83. package/dist/sig/mod.d.ts +2 -2
  84. package/dist/sig/mod.js +2 -2
  85. package/dist/sig/owner.test.mjs +1 -1
  86. package/dist/sig/portable-collection.test.mjs +1 -1
  87. package/dist/sig/portable-key.test.mjs +6 -6
  88. package/dist/sig/proof.test.mjs +3 -3
  89. package/dist/{temporal-q6FSekcN.mjs → temporal-Dhsk6aUT.mjs} +1 -1
  90. package/dist/testing/kv.test.mjs +1 -1
  91. package/dist/testing/mod.d.mts +118 -1
  92. package/dist/utils/docloader.test.mjs +2 -2
  93. package/dist/utils/kv-cache.test.mjs +1 -1
  94. package/dist/utils/mod.cjs +1 -1
  95. package/dist/utils/mod.d.cts +1 -1
  96. package/dist/utils/mod.d.ts +1 -1
  97. package/dist/utils/mod.js +1 -1
  98. package/package.json +6 -6
@@ -0,0 +1,348 @@
1
+ import "temporal-polyfill";
2
+ import "urlpattern-polyfill";
3
+ globalThis.addEventListener = () => {};
4
+ import { t as MemoryKvStore } from "../kv-DCHkaJ9-.mjs";
5
+ import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
6
+ import { t as assertFalse } from "../assert_false-CPdRnc3E.mjs";
7
+ import "../std__assert-DvMDRPes.mjs";
8
+ import { t as assert } from "../assert-D1Eafmd_.mjs";
9
+ import { r as ed25519PublicKey } from "../keys-DIJNHW-Y.mjs";
10
+ import { o as createFederation } from "../middleware-B99JjvFn.mjs";
11
+ import { Note, Object as Object$1, Person } from "@fedify/vocab";
12
+ import { test } from "@fedify/fixture";
13
+ import { exportDidKey, parseIri } from "@fedify/vocab-runtime";
14
+ //#region src/federation/portable-parse-uri.test.ts
15
+ const did = await exportDidKey(ed25519PublicKey.publicKey);
16
+ const otherDid = await exportDidKey((await crypto.subtle.generateKey("Ed25519", true, ["sign", "verify"])).publicKey);
17
+ const encodedDid = encodeURIComponent(did);
18
+ const ORIGIN = "https://example.com";
19
+ const NOTE_TYPE_ID = new URL("https://www.w3.org/ns/activitystreams#Note");
20
+ const OBJECT_TYPE_ID = new URL("https://www.w3.org/ns/activitystreams#Object");
21
+ function createTestFederation() {
22
+ const calls = [];
23
+ const federation = createFederation({ kv: new MemoryKvStore() });
24
+ federation.setActorDispatcher("/users/{identifier}", (_ctx, identifier) => {
25
+ calls.push(`actor:${identifier}`);
26
+ return new Person({});
27
+ }).mapActorAlias("/bot", "bot").mapPortableActorId((_ctx, identifier) => {
28
+ calls.push(`portableActorId:${identifier}`);
29
+ return null;
30
+ });
31
+ federation.setObjectDispatcher(Note, "/users/{identifier}/notes/{id}", () => {
32
+ calls.push("object");
33
+ return null;
34
+ });
35
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox");
36
+ federation.setOutboxDispatcher("/users/{identifier}/outbox", () => null);
37
+ federation.setFollowingDispatcher("/users/{identifier}/following", () => null);
38
+ federation.setFollowersDispatcher("/users/{identifier}/followers", () => null);
39
+ federation.setLikedDispatcher("/users/{identifier}/liked", () => null);
40
+ federation.setFeaturedDispatcher("/users/{identifier}/featured", () => null);
41
+ federation.setFeaturedTagsDispatcher("/users/{identifier}/tags", () => null);
42
+ federation.setCollectionDispatcher("bookmarks", Object$1, "/users/{identifier}/bookmarks", () => null).mapPortableOwner((_ctx, values) => {
43
+ calls.push(`portableOwner:${values.identifier}`);
44
+ return values.identifier;
45
+ });
46
+ federation.setOrderedCollectionDispatcher("pins", Note, "/users/{identifier}/pins", () => null);
47
+ return {
48
+ federation,
49
+ ctx: federation.createContext(new URL(ORIGIN)),
50
+ calls
51
+ };
52
+ }
53
+ function portableId(path, authority = did) {
54
+ return parseIri(`ap+ef61://${authority}${path}`);
55
+ }
56
+ function compatibleId(path, origin = ORIGIN, authority = did) {
57
+ return new URL(`${origin}/.well-known/apgateway/${authority}${path}`);
58
+ }
59
+ function parsePortable(ctx, uri) {
60
+ return ctx.parseUri(uri, { portable: true });
61
+ }
62
+ test("Context.parseUri() does not recognize portable IDs by default", () => {
63
+ const { ctx } = createTestFederation();
64
+ for (const uri of [
65
+ portableId("/users/alice"),
66
+ parseIri(`ap://${did}/users/alice`),
67
+ compatibleId("/users/alice"),
68
+ compatibleId("/users/alice", "https://gateway.example")
69
+ ]) {
70
+ assertEquals(ctx.parseUri(uri), null, uri.href);
71
+ assertEquals(ctx.parseUri(uri, {}), null, uri.href);
72
+ assertEquals(ctx.parseUri(uri, { portable: false }), null, uri.href);
73
+ }
74
+ });
75
+ test("Context.parseUri() keeps the results of ordinary URIs", () => {
76
+ const { ctx } = createTestFederation();
77
+ for (const path of [
78
+ "/users/alice",
79
+ "/bot",
80
+ "/users/alice/notes/123",
81
+ "/inbox",
82
+ "/users/alice/inbox",
83
+ "/users/alice/outbox",
84
+ "/users/alice/following",
85
+ "/users/alice/followers",
86
+ "/users/alice/liked",
87
+ "/users/alice/featured",
88
+ "/users/alice/tags",
89
+ "/users/alice/bookmarks",
90
+ "/users/alice/pins",
91
+ "/nowhere"
92
+ ]) {
93
+ const uri = new URL(path, ORIGIN);
94
+ const result = ctx.parseUri(uri);
95
+ assertEquals(parsePortable(ctx, uri), result, path);
96
+ if (result != null) assertFalse("authority" in result, path);
97
+ }
98
+ assertEquals(parsePortable(ctx, new URL("https://other.example/users/alice")), null);
99
+ assertEquals(ctx.parseUri(null, { portable: true }), null);
100
+ });
101
+ test("Context.parseUri() recognizes portable IDs", () => {
102
+ const { ctx, calls } = createTestFederation();
103
+ const identifier = "alice";
104
+ const cases = [
105
+ ["/users/alice", {
106
+ type: "actor",
107
+ identifier,
108
+ authority: did
109
+ }],
110
+ ["/bot", {
111
+ type: "actor",
112
+ identifier: "bot",
113
+ authority: did
114
+ }],
115
+ ["/users/alice/notes/123", {
116
+ type: "object",
117
+ class: Note,
118
+ typeId: NOTE_TYPE_ID,
119
+ values: {
120
+ identifier,
121
+ id: "123"
122
+ },
123
+ authority: did
124
+ }],
125
+ ["/users/alice/inbox", {
126
+ type: "inbox",
127
+ identifier,
128
+ authority: did
129
+ }],
130
+ ["/users/alice/outbox", {
131
+ type: "outbox",
132
+ identifier,
133
+ authority: did
134
+ }],
135
+ ["/users/alice/following", {
136
+ type: "following",
137
+ identifier,
138
+ authority: did
139
+ }],
140
+ ["/users/alice/followers", {
141
+ type: "followers",
142
+ identifier,
143
+ authority: did
144
+ }],
145
+ ["/users/alice/liked", {
146
+ type: "liked",
147
+ identifier,
148
+ authority: did
149
+ }],
150
+ ["/users/alice/featured", {
151
+ type: "featured",
152
+ identifier,
153
+ authority: did
154
+ }],
155
+ ["/users/alice/tags", {
156
+ type: "featuredTags",
157
+ identifier,
158
+ authority: did
159
+ }],
160
+ ["/users/alice/bookmarks", {
161
+ type: "collection",
162
+ name: "bookmarks",
163
+ class: Object$1,
164
+ typeId: OBJECT_TYPE_ID,
165
+ values: { identifier },
166
+ authority: did
167
+ }],
168
+ ["/users/alice/pins", {
169
+ type: "orderedCollection",
170
+ name: "pins",
171
+ class: Note,
172
+ typeId: NOTE_TYPE_ID,
173
+ values: { identifier },
174
+ authority: did
175
+ }]
176
+ ];
177
+ for (const [path, expected] of cases) for (const uri of [
178
+ portableId(path),
179
+ parseIri(`ap://${did}${path}`),
180
+ new URL(`ap+ef61://${encodedDid}${path}`),
181
+ new URL(`ap://${encodedDid}${path}`),
182
+ compatibleId(path),
183
+ compatibleId(path, "https://gateway.example"),
184
+ compatibleId(path, "http://localhost:8000")
185
+ ]) assertEquals(parsePortable(ctx, uri), expected, uri.href);
186
+ assertEquals(calls, []);
187
+ });
188
+ test("Context.parseUri() round-trips the portable ID helpers", () => {
189
+ const { ctx } = createTestFederation();
190
+ const uris = [
191
+ [ctx.getPortableActorUri("alice", did), "actor"],
192
+ [ctx.getPortableObjectUri(Note, {
193
+ identifier: "alice",
194
+ id: "1"
195
+ }, did), "object"],
196
+ [ctx.getPortableInboxUri("alice", did), "inbox"],
197
+ [ctx.getPortableOutboxUri("alice", did), "outbox"],
198
+ [ctx.getPortableFollowingUri("alice", did), "following"],
199
+ [ctx.getPortableFollowersUri("alice", did), "followers"],
200
+ [ctx.getPortableLikedUri("alice", did), "liked"],
201
+ [ctx.getPortableFeaturedUri("alice", did), "featured"],
202
+ [ctx.getPortableFeaturedTagsUri("alice", did), "featuredTags"],
203
+ [ctx.getPortableCollectionUri("bookmarks", { identifier: "alice" }, did), "collection"],
204
+ [ctx.getPortableCollectionUri("pins", { identifier: "alice" }, did), "orderedCollection"]
205
+ ];
206
+ for (const [uri, type] of uris) {
207
+ const result = parsePortable(ctx, uri);
208
+ assertEquals(result?.type, type, uri.href);
209
+ assertEquals(result?.authority, did, uri.href);
210
+ }
211
+ });
212
+ test("Context.parseUri() returns any DID for the caller to check", () => {
213
+ const { ctx } = createTestFederation();
214
+ assertEquals(parsePortable(ctx, portableId("/users/alice", otherDid)), {
215
+ type: "actor",
216
+ identifier: "alice",
217
+ authority: otherDid
218
+ });
219
+ assertEquals(parsePortable(ctx, compatibleId("/users/alice", "https://gateway.example", otherDid)), {
220
+ type: "actor",
221
+ identifier: "alice",
222
+ authority: otherDid
223
+ });
224
+ assertEquals(parsePortable(ctx, portableId("/users/alice", "did:web:example.com")), {
225
+ type: "actor",
226
+ identifier: "alice",
227
+ authority: "did:web:example.com"
228
+ });
229
+ assertEquals(parsePortable(ctx, parseIri(`ap://DID:KEY:${did.slice(8)}/users/alice`)), {
230
+ type: "actor",
231
+ identifier: "alice",
232
+ authority: did
233
+ });
234
+ assertEquals(parsePortable(ctx, new URL(`ap://DID%3Akey%3A${did.slice(8)}/users/alice`)), {
235
+ type: "actor",
236
+ identifier: "alice",
237
+ authority: did
238
+ });
239
+ });
240
+ test("Context.parseUri() ignores the query and fragment of portable IDs", () => {
241
+ const { ctx } = createTestFederation();
242
+ const expected = {
243
+ type: "actor",
244
+ identifier: "alice",
245
+ authority: did
246
+ };
247
+ const hint = "@gateway=https%3A%2F%2Fgateway.example";
248
+ for (const uri of [
249
+ parseIri(`ap://${did}/users/alice?${hint}`),
250
+ portableId("/users/alice#main-key"),
251
+ new URL(`${compatibleId("/users/alice").href}?${hint}`),
252
+ new URL(`${compatibleId("/users/alice").href}#main-key`)
253
+ ]) assertEquals(parsePortable(ctx, uri), expected, uri.href);
254
+ });
255
+ test("Context.parseUri() routes portable paths like ordinary ones", () => {
256
+ const { ctx } = createTestFederation();
257
+ for (const path of [
258
+ "/users/alice/",
259
+ "/users/alice/notes/",
260
+ "/users"
261
+ ]) {
262
+ const ordinary = ctx.parseUri(new URL(path, ORIGIN));
263
+ const expected = ordinary == null ? null : {
264
+ ...ordinary,
265
+ authority: did
266
+ };
267
+ assertEquals(parsePortable(ctx, portableId(path)), expected, path);
268
+ assertEquals(parsePortable(ctx, compatibleId(path)), expected, path);
269
+ }
270
+ });
271
+ test("Context.parseUri() does not recognize unroutable portable IDs", () => {
272
+ const { ctx } = createTestFederation();
273
+ for (const uri of [
274
+ portableId("/nowhere"),
275
+ compatibleId("/nowhere"),
276
+ portableId("/inbox"),
277
+ compatibleId("/inbox"),
278
+ compatibleId("/inbox", "https://gateway.example"),
279
+ new URL(`${ORIGIN}/.well-known/apgateway`),
280
+ new URL(`${ORIGIN}/.well-known/apgateway/users/alice`),
281
+ new URL(`${ORIGIN}/.well-known/apgateway/hl:zQmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n`)
282
+ ]) assertEquals(parsePortable(ctx, uri), null, uri.href);
283
+ });
284
+ test("Context.parseUri() does not recognize malformed portable IDs", () => {
285
+ const { ctx } = createTestFederation();
286
+ const base64UrlDid = `did:key:u${did.slice(8).slice(1)}`;
287
+ for (const uri of [
288
+ new URL(`ap+ef61://${encodeURIComponent(base64UrlDid)}/users/alice`),
289
+ compatibleId("/users/alice", ORIGIN, base64UrlDid),
290
+ new URL("ap+ef61://did%3Akey/users/alice"),
291
+ new URL("ap+ef61://example.com/users/alice"),
292
+ compatibleId("/users/alice", ORIGIN, "did:"),
293
+ compatibleId("/users/alice", "https://gateway.example", "did:key"),
294
+ new URL(`ap+ef61://${encodedDid}/users/%ZZ`),
295
+ new URL(`${compatibleId("/users/").href}%ZZ`),
296
+ new URL(`ap+ef61://user:pass@${encodedDid}/users/alice`),
297
+ new URL(`https://user:pass@gateway.example/.well-known/apgateway/${did}/users/alice`)
298
+ ]) assertEquals(parsePortable(ctx, uri), null, uri.href);
299
+ });
300
+ test("Context.parseUri() prefers the application's routes", () => {
301
+ const federation = createFederation({ kv: new MemoryKvStore() });
302
+ federation.setActorDispatcher("/users/{identifier}", () => null);
303
+ federation.setObjectDispatcher(Note, "/.well-known/apgateway/{+did}/users/{identifier}/notes/{id}", () => null);
304
+ federation.setNodeInfoDispatcher(`/.well-known/apgateway/${did}/users/alice`, () => ({
305
+ software: {
306
+ name: "test",
307
+ version: "1.0.0"
308
+ },
309
+ protocols: ["activitypub"],
310
+ usage: {
311
+ users: {},
312
+ localPosts: 0,
313
+ localComments: 0
314
+ }
315
+ }));
316
+ const ctx = federation.createContext(new URL(ORIGIN));
317
+ assertEquals(parsePortable(ctx, compatibleId("/users/alice")), null);
318
+ assertEquals(parsePortable(ctx, compatibleId("/users/alice", "https://gateway.example")), {
319
+ type: "actor",
320
+ identifier: "alice",
321
+ authority: did
322
+ });
323
+ const note = parsePortable(ctx, compatibleId("/users/alice/notes/1"));
324
+ assert(note?.type === "object");
325
+ assertEquals(note.values, {
326
+ did,
327
+ identifier: "alice",
328
+ id: "1"
329
+ });
330
+ assertFalse("authority" in note);
331
+ assertEquals(parsePortable(ctx, portableId("/users/alice")), {
332
+ type: "actor",
333
+ identifier: "alice",
334
+ authority: did
335
+ });
336
+ });
337
+ test("RequestContext.parseUri() recognizes portable IDs", () => {
338
+ const { federation } = createTestFederation();
339
+ const ctx = federation.createContext(new Request(compatibleId("/users/alice")), void 0);
340
+ assertEquals(ctx.parseUri(portableId("/users/alice")), null);
341
+ assertEquals(ctx.parseUri(portableId("/users/alice", otherDid), { portable: true }), {
342
+ type: "actor",
343
+ identifier: "alice",
344
+ authority: otherDid
345
+ });
346
+ });
347
+ //#endregion
348
+ export {};
@@ -8,10 +8,10 @@ import { t as assertThrows } from "../assert_throws-BYITOvPO.mjs";
8
8
  import "../std__assert-DvMDRPes.mjs";
9
9
  import { t as assertStrictEquals } from "../assert_strict_equals-CYvBmkpC.mjs";
10
10
  import { t as assert } from "../assert-D1Eafmd_.mjs";
11
- import { a as signObject, c as verifyPortableObjectProof } from "../proof-C2YwD8a_.mjs";
12
- import { l as signRequest } from "../http-CGcLv8rh.mjs";
11
+ import { a as signObject, c as verifyPortableObjectProof } from "../proof-CRTPWbVh.mjs";
12
+ import { l as signRequest } from "../http-DRH0n8TD.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-xdukzV3x.mjs";
14
+ import { b as PORTABLE_OBJECT_CONTENT_TYPE, o as createFederation } from "../middleware-B99JjvFn.mjs";
15
15
  import { configure, reset } from "@logtape/logtape";
16
16
  import { Collection, Create, Note, Object as Object$1, Person, Tombstone, lookupObject } from "@fedify/vocab";
17
17
  import { mockDocumentLoader, test } from "@fedify/fixture";
@@ -10,10 +10,10 @@ import { t as assertNotEquals } from "../assert_not_equals-CpPK2t89.mjs";
10
10
  import "../std__assert-DvMDRPes.mjs";
11
11
  import { t as assertRejects } from "../assert_rejects-CTptdYck.mjs";
12
12
  import { t as assert } from "../assert-D1Eafmd_.mjs";
13
- import { d as verifyRequest } from "../http-CGcLv8rh.mjs";
13
+ import { d as verifyRequest } from "../http-DRH0n8TD.mjs";
14
14
  import { i as rsaPrivateKey2, n as ed25519PrivateKey, s as rsaPublicKey2, t as ed25519Multikey } from "../keys-DIJNHW-Y.mjs";
15
- import { t as doesActorOwnKey } from "../owner-D63yPo_i.mjs";
16
- import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "../send-D9SGhxTB.mjs";
15
+ import { t as doesActorOwnKey } from "../owner-DKdtsrEq.mjs";
16
+ import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "../send-2dULlZc8.mjs";
17
17
  import { Activity, Application, Endpoints, Group, Person, Service } from "@fedify/vocab";
18
18
  import { createTestMeterProvider, createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
19
19
  import { FetchError, UrlError } from "@fedify/vocab-runtime";
@@ -4,7 +4,7 @@ globalThis.addEventListener = () => {};
4
4
  import { t as MemoryKvStore } from "../../kv-DCHkaJ9-.mjs";
5
5
  import { a as numberSchema, i as makeSchema, n as baseOptions, o as stringSchema, t as MockQueue } from "../../tasks-IG3pHfvZ.mjs";
6
6
  import "../../testing-C1itIjqW.mjs";
7
- import { o as createFederation } from "../../middleware-xdukzV3x.mjs";
7
+ import { o as createFederation } from "../../middleware-B99JjvFn.mjs";
8
8
  import { n as ParallelMessageQueue } from "../../mq-Dm1Wmtm-.mjs";
9
9
  import { configure, reset } from "@logtape/logtape";
10
10
  import { delay } from "es-toolkit";
@@ -4,8 +4,8 @@ globalThis.addEventListener = () => {};
4
4
  import { t as MemoryKvStore } from "../../kv-DCHkaJ9-.mjs";
5
5
  import { a as numberSchema, i as makeSchema, n as baseOptions, o as stringSchema, t as MockQueue } from "../../tasks-IG3pHfvZ.mjs";
6
6
  import "../../testing-C1itIjqW.mjs";
7
- import { o as createFederation } from "../../middleware-xdukzV3x.mjs";
8
- import { r as createFederationBuilder } from "../../builder-CLaS4-yA.mjs";
7
+ import { o as createFederation } from "../../middleware-B99JjvFn.mjs";
8
+ import { r as createFederationBuilder } from "../../builder-BqRmnx7p.mjs";
9
9
  import { t as TaskCodec } from "../../codec-C6rTrpIp.mjs";
10
10
  import { t as InProcessMessageQueue } from "../../mq-Dm1Wmtm-.mjs";
11
11
  import { W3CTraceContextPropagator } from "@opentelemetry/core";
@@ -4,7 +4,7 @@ globalThis.addEventListener = () => {};
4
4
  import { t as assertFalse } from "../assert_false-CPdRnc3E.mjs";
5
5
  import "../std__assert-DvMDRPes.mjs";
6
6
  import { t as assert } from "../assert-D1Eafmd_.mjs";
7
- import { t as hasMalformedKnownTemporalLiteral } from "../temporal-q6FSekcN.mjs";
7
+ import { t as hasMalformedKnownTemporalLiteral } from "../temporal-Dhsk6aUT.mjs";
8
8
  import { test } from "@fedify/fixture";
9
9
  //#region src/federation/temporal.test.ts
10
10
  test("hasMalformedKnownTemporalLiteral() detects expanded proof timestamps", async () => {
@@ -6,7 +6,7 @@ import { t as MemoryKvStore } from "../kv-DCHkaJ9-.mjs";
6
6
  import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
7
7
  import { t as assertNotEquals } from "../assert_not_equals-CpPK2t89.mjs";
8
8
  import "../std__assert-DvMDRPes.mjs";
9
- import { o as createFederation, s as handleWebFinger } from "../middleware-xdukzV3x.mjs";
9
+ import { o as createFederation, s as handleWebFinger } from "../middleware-B99JjvFn.mjs";
10
10
  import { Image, Link, Person, Tombstone } from "@fedify/vocab";
11
11
  import { createTestMeterProvider, test } from "@fedify/fixture";
12
12
  import { parseIri } from "@fedify/vocab-runtime";
@@ -1,10 +1,10 @@
1
1
  const { URLPattern } = require("urlpattern-polyfill");
2
2
  const require_rolldown_runtime = require("./rolldown-runtime-Dmm3t92W.cjs");
3
3
  const require_transformers = require("./transformers-BalCrSsr.cjs");
4
- const require_http = require("./http-BObQoB0j.cjs");
4
+ const require_http = require("./http-R4g1McwE.cjs");
5
5
  const require_types = require("./types-poIao40f.cjs");
6
- const require_portable_collection = require("./portable-collection-B8ojoQ50.cjs");
7
- const require_kv_cache = require("./kv-cache-D2fnB8qu.cjs");
6
+ const require_portable_collection = require("./portable-collection-UajqDrSL.cjs");
7
+ const require_kv_cache = require("./kv-cache-Ct-KWRta.cjs");
8
8
  let _logtape_logtape = require("@logtape/logtape");
9
9
  let _fedify_uri_template = require("@fedify/uri-template");
10
10
  let _fedify_vocab = require("@fedify/vocab");
@@ -2497,10 +2497,13 @@ async function routeActivity({ context: ctx, json, originalJson, normalizedActiv
2497
2497
  const Temporal$9 = require_http.Temporal;
2498
2498
  const KEY_CACHE_GENERATION = "2";
2499
2499
  const COMPATIBLE_KEY_SEGMENT = "__compatible";
2500
- const MAX_COMPATIBLE_KEY_TTL = Temporal$9.Duration.from({ hours: 1 });
2500
+ const MAX_OWNER_BYTES = 32 * 1024;
2501
2501
  function isCompatibleKeyEntry(value) {
2502
2502
  return value != null && typeof value === "object" && "key" in value && "expires" in value && typeof value.expires === "number";
2503
2503
  }
2504
+ function isJsonObject(value) {
2505
+ return value != null && typeof value === "object" && !Array.isArray(value);
2506
+ }
2504
2507
  var KvKeyCache = class {
2505
2508
  kv;
2506
2509
  prefix;
@@ -2582,30 +2585,48 @@ var KvKeyCache = class {
2582
2585
  keyId.href
2583
2586
  ];
2584
2587
  const isExpired = (entry) => entry.expires <= now().epochMilliseconds;
2585
- const keyTtl = Math.min(this.keyTtl.total("millisecond"), MAX_COMPATIBLE_KEY_TTL.total("millisecond"));
2588
+ const keyTtl = Math.min(this.keyTtl.total("millisecond"), require_http.MAX_PORTABLE_KEY_TTL.total("millisecond"));
2586
2589
  const unavailableKeyTtl = this.unavailableKeyTtl.total("millisecond");
2587
- return {
2588
- get: async (keyId) => {
2589
- const entry = await this.kv.get(entryKey(keyId));
2590
- if (entry === void 0) return void 0;
2591
- if (!isCompatibleKeyEntry(entry) || isExpired(entry)) {
2592
- await this.kv.delete(entryKey(keyId));
2593
- return;
2594
- }
2595
- if (entry.key === null) return null;
2596
- let key;
2590
+ const getEntry = async (keyId) => {
2591
+ const entry = await this.kv.get(entryKey(keyId));
2592
+ if (entry === void 0) return void 0;
2593
+ if (!isCompatibleKeyEntry(entry) || isExpired(entry)) {
2594
+ await this.kv.delete(entryKey(keyId));
2595
+ return;
2596
+ }
2597
+ if (entry.key === null) return null;
2598
+ let expires;
2599
+ try {
2600
+ expires = Temporal$9.Instant.fromEpochMilliseconds(entry.expires);
2601
+ } catch (error) {
2602
+ if (!(error instanceof RangeError)) throw error;
2603
+ await this.kv.delete(entryKey(keyId));
2604
+ return;
2605
+ }
2606
+ let key;
2607
+ try {
2608
+ key = await _fedify_vocab.CryptographicKey.fromJsonLd(entry.key, this.options);
2609
+ } catch {
2597
2610
  try {
2598
- key = await _fedify_vocab.CryptographicKey.fromJsonLd(entry.key, this.options);
2611
+ key = await _fedify_vocab.Multikey.fromJsonLd(entry.key, this.options);
2599
2612
  } catch {
2600
- try {
2601
- key = await _fedify_vocab.Multikey.fromJsonLd(entry.key, this.options);
2602
- } catch {
2603
- await this.kv.delete(entryKey(keyId));
2604
- return;
2605
- }
2613
+ await this.kv.delete(entryKey(keyId));
2614
+ return;
2606
2615
  }
2607
- return isExpired(entry) ? void 0 : key;
2616
+ }
2617
+ if (isExpired(entry)) return void 0;
2618
+ return {
2619
+ key,
2620
+ ...isJsonObject(entry.owner) ? { owner: entry.owner } : {},
2621
+ expires
2622
+ };
2623
+ };
2624
+ return {
2625
+ get: async (keyId) => {
2626
+ const entry = await getEntry(keyId);
2627
+ return entry == null ? entry : entry.key;
2608
2628
  },
2629
+ getEntry,
2609
2630
  set: async (keyId, key, options) => {
2610
2631
  const serialized = key == null ? null : await key.toJsonLd(this.options);
2611
2632
  const current = now();
@@ -2616,10 +2637,13 @@ var KvKeyCache = class {
2616
2637
  await this.kv.delete(entryKey(keyId));
2617
2638
  return;
2618
2639
  }
2640
+ const owner = key == null || options?.owner == null || new TextEncoder().encode(JSON.stringify(options.owner)).length > MAX_OWNER_BYTES ? void 0 : options.owner;
2619
2641
  await this.kv.set(entryKey(keyId), {
2620
2642
  key: serialized,
2621
- expires
2643
+ expires,
2644
+ ...owner == null ? {} : { owner }
2622
2645
  }, { ttl: Temporal$9.Duration.from({ milliseconds: ttl }) });
2646
+ return key == null ? void 0 : Temporal$9.Instant.fromEpochMilliseconds(expires);
2623
2647
  },
2624
2648
  delete: async (keyId) => {
2625
2649
  await this.kv.delete(entryKey(keyId));
@@ -2823,6 +2847,43 @@ function parsePortableGatewayRequest(url) {
2823
2847
  };
2824
2848
  }
2825
2849
  /**
2850
+ * Recognizes an FEP-ef61 portable ID, i.e., an `ap:` or `ap+ef61:` URI, or
2851
+ * a compatible identifier on any gateway, and splits it into its DID and
2852
+ * the object path to route.
2853
+ *
2854
+ * It accepts the same IDs as the gateway endpoint does: the query, e.g.,
2855
+ * location hints, and the fragment are ignored, and a malformed DID, or
2856
+ * a `did:key` DID that is not encoded in base58-btc, is refused.
2857
+ *
2858
+ * @param uri The ID to recognize.
2859
+ * @returns The DID normalized as an FEP-fe34 origin, and the object path,
2860
+ * or `null` if the ID is not a portable ID or is malformed.
2861
+ */
2862
+ function parsePortableId(uri) {
2863
+ if (uri.username !== "" || uri.password !== "") return null;
2864
+ if (uri.protocol !== "ap:" && uri.protocol !== "ap+ef61:") {
2865
+ const request = parsePortableGatewayRequest(uri);
2866
+ if (request?.type !== "object") return null;
2867
+ return {
2868
+ authority: request.portableRequest.authority,
2869
+ path: request.path
2870
+ };
2871
+ }
2872
+ try {
2873
+ const id = (0, _fedify_vocab_runtime.parseIri)(uri);
2874
+ (0, _fedify_vocab_runtime.canonicalizePortableUri)((0, _fedify_vocab_runtime.formatIri)(id));
2875
+ const authority = (0, _fedify_vocab_runtime.getFe34Origin)(id);
2876
+ assertBase58BtcDidKey(authority);
2877
+ return {
2878
+ authority,
2879
+ path: id.pathname
2880
+ };
2881
+ } catch (error) {
2882
+ if (error instanceof TypeError) return null;
2883
+ throw error;
2884
+ }
2885
+ }
2886
+ /**
2826
2887
  * Builds a portable ID from a DID authority and an object path.
2827
2888
  * @param authority The bare DID, e.g., `did:key:z6Mk...`.
2828
2889
  * @param path The object path, e.g., `/notes/123`.
@@ -9484,12 +9545,25 @@ var ContextImpl = class ContextImpl {
9484
9545
  if (path === null) throw new _fedify_uri_template.RouterError(`No collection dispatcher registered for "${String(name)}".`);
9485
9546
  return buildPortableUri(requirePortableAuthority(authority), path);
9486
9547
  }
9487
- parseUri(uri) {
9548
+ parseUri(uri, options = {}) {
9488
9549
  if (uri == null) return null;
9489
- if (uri.origin !== this.origin && uri.origin !== this.canonicalOrigin) return null;
9490
- const route = this.federation.router.route(uri.pathname);
9491
- if (route == null) return null;
9492
- else if (route.name === "sharedInbox") return {
9550
+ if (uri.origin === this.origin || uri.origin === this.canonicalOrigin) {
9551
+ const route = this.federation.router.route(uri.pathname);
9552
+ if (route != null) return this.#parseRoute(route);
9553
+ }
9554
+ if (!options.portable) return null;
9555
+ const portable = parsePortableId(uri);
9556
+ if (portable == null) return null;
9557
+ const route = this.federation.router.route(portable.path);
9558
+ if (route == null || route.name === "sharedInbox") return null;
9559
+ const result = this.#parseRoute(route);
9560
+ return result == null ? null : {
9561
+ ...result,
9562
+ authority: portable.authority
9563
+ };
9564
+ }
9565
+ #parseRoute(route) {
9566
+ if (route.name === "sharedInbox") return {
9493
9567
  type: "inbox",
9494
9568
  identifier: void 0
9495
9569
  };