matrix-nodejs-homeserver 0.2.0 → 0.3.2

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 (99) hide show
  1. package/README.md +89 -11
  2. package/dist/adapters/cluster.d.ts +1 -26
  3. package/dist/adapters/cluster.js +50 -103
  4. package/dist/api/account.js +407 -354
  5. package/dist/api/admin.js +82 -61
  6. package/dist/api/appservice.js +37 -25
  7. package/dist/api/backup.js +73 -62
  8. package/dist/api/federation.js +172 -138
  9. package/dist/api/filters.js +4 -3
  10. package/dist/api/legacy.js +91 -78
  11. package/dist/api/media.js +71 -55
  12. package/dist/api/misc.js +46 -37
  13. package/dist/api/preview.js +37 -73
  14. package/dist/api/profile.js +108 -97
  15. package/dist/api/push.js +114 -99
  16. package/dist/api/relationships.js +64 -58
  17. package/dist/api/rooms.js +703 -580
  18. package/dist/api/rtc.d.ts +0 -1
  19. package/dist/api/rtc.js +48 -34
  20. package/dist/api/search.js +133 -127
  21. package/dist/api/slidingsync.js +41 -31
  22. package/dist/api/spaces.js +82 -77
  23. package/dist/api/sync.js +325 -135
  24. package/dist/api/threadsubs.js +72 -61
  25. package/dist/api/threepid.js +99 -66
  26. package/dist/api/uia.js +17 -16
  27. package/dist/api/user.js +221 -178
  28. package/dist/core/admin.d.ts +0 -1
  29. package/dist/core/admin.js +6 -5
  30. package/dist/core/adminroom.js +9 -8
  31. package/dist/core/appservices.d.ts +2 -1
  32. package/dist/core/appservices.js +5 -4
  33. package/dist/core/audit.d.ts +0 -1
  34. package/dist/core/authrules.js +26 -25
  35. package/dist/core/cluster.d.ts +12 -5
  36. package/dist/core/cluster.js +46 -29
  37. package/dist/core/delayed.js +2 -1
  38. package/dist/core/events.js +11 -10
  39. package/dist/core/html.js +13 -0
  40. package/dist/core/identity.js +1 -1
  41. package/dist/core/invitefilter.js +2 -1
  42. package/dist/core/json.js +10 -1
  43. package/dist/core/keys.d.ts +10 -1
  44. package/dist/core/keys.js +62 -42
  45. package/dist/core/m.d.ts +209 -0
  46. package/dist/core/m.js +182 -0
  47. package/dist/core/outbound.d.ts +16 -0
  48. package/dist/core/outbound.js +172 -0
  49. package/dist/core/partialstate.js +2 -1
  50. package/dist/core/policy.js +4 -3
  51. package/dist/core/push.d.ts +0 -1
  52. package/dist/core/push.js +41 -28
  53. package/dist/core/pushrules.js +36 -35
  54. package/dist/core/ratelimit.d.ts +1 -1
  55. package/dist/core/rooms.d.ts +10 -1
  56. package/dist/core/rooms.js +101 -74
  57. package/dist/core/semaphore.js +23 -0
  58. package/dist/core/sso.d.ts +15 -1
  59. package/dist/core/sso.js +38 -2
  60. package/dist/core/stateres.js +7 -6
  61. package/dist/core/threepid.js +1 -9
  62. package/dist/federation/client.js +9 -1
  63. package/dist/federation/index.d.ts +2 -2
  64. package/dist/federation/index.js +43 -35
  65. package/dist/homeserver.d.ts +23 -4
  66. package/dist/homeserver.js +12 -3
  67. package/dist/http/handler.js +2 -23
  68. package/dist/http/input.js +2 -2
  69. package/dist/http/procedure.js +25 -0
  70. package/dist/http/router.d.ts +9 -26
  71. package/dist/http/router.js +10 -30
  72. package/dist/index.d.ts +4 -3
  73. package/dist/index.js +3 -2
  74. package/dist/storage/batch.js +11 -0
  75. package/dist/storage/memory.js +1 -0
  76. package/dist/storage/schema.js +1 -1
  77. package/dist/storage/sql.js +20 -1
  78. package/dist/storage/types.d.ts +16 -0
  79. package/dist/vendor/zod/LICENSE +21 -0
  80. package/dist/vendor/zod/v4/classic/errors.js +54 -0
  81. package/dist/vendor/zod/v4/classic/parse.js +17 -0
  82. package/dist/vendor/zod/v4/classic/schemas.js +865 -0
  83. package/dist/vendor/zod/v4/core/api.js +512 -0
  84. package/dist/vendor/zod/v4/core/checks.js +300 -0
  85. package/dist/vendor/zod/v4/core/core.js +109 -0
  86. package/dist/vendor/zod/v4/core/doc.js +35 -0
  87. package/dist/vendor/zod/v4/core/errors.js +118 -0
  88. package/dist/vendor/zod/v4/core/json-schema-processors.js +428 -0
  89. package/dist/vendor/zod/v4/core/memoizer.js +255 -0
  90. package/dist/vendor/zod/v4/core/parse.js +206 -0
  91. package/dist/vendor/zod/v4/core/regexes.js +73 -0
  92. package/dist/vendor/zod/v4/core/registries.js +48 -0
  93. package/dist/vendor/zod/v4/core/schemas.js +1378 -0
  94. package/dist/vendor/zod/v4/core/to-json-schema.js +448 -0
  95. package/dist/vendor/zod/v4/core/util.js +537 -0
  96. package/dist/vendor/zod/v4/core/versions.js +8 -0
  97. package/dist/vendor/zod/v4/locales/en.js +110 -0
  98. package/package.json +8 -6
  99. package/dist/core/pushrules.d.ts +0 -1
@@ -1,5 +1,6 @@
1
1
  import { isObject } from "../core/json.js";
2
2
  import { randomToken, signJson, verifyJsonSignature } from "../core/crypto.js";
3
+ import { M } from "../core/m.js";
3
4
  import { SUPPORTED_ROOM_VERSIONS, eventIdOf, redact, roomVersionFeatures, splitStateKey, stateKey } from "../core/events.js";
4
5
  import { MatrixError, errors } from "../core/errors.js";
5
6
  import { isValidUserId } from "../core/ids.js";
@@ -7,26 +8,27 @@ import { authEventKeys, checkAuth, getState, membershipOf, powerLevels, userLeve
7
8
  import { inviteDecision } from "../core/invitefilter.js";
8
9
  import { parseXMatrix } from "../federation/client.js";
9
10
  import { domainOf, serverAllowedByAcl } from "../federation/index.js";
11
+ import { array, looseObject, record, string, unknown } from "../vendor/zod/v4/classic/schemas.js";
10
12
  import { body, lenient } from "../http/input.js";
13
+ import { procedure } from "../http/procedure.js";
11
14
  import { roomDetails } from "./spaces.js";
12
15
  import { fullProfile } from "../core/profile.js";
13
16
  import { relationshipsForServer } from "./relationships.js";
14
17
  import { canonicalJsonResponse } from "../http/handler.js";
15
- import { z } from "zod";
16
18
  //#region src/api/federation.ts
17
- const objectMap = lenient(z.record(z.string(), z.unknown()));
19
+ const objectMap = lenient(record(string(), unknown()));
18
20
  const keyQueryBody = body({ server_keys: objectMap });
19
21
  const missingEventsBody = body({
20
- earliest_events: lenient(z.array(z.string())),
21
- latest_events: lenient(z.array(z.string())),
22
- limit: z.unknown().optional(),
23
- min_depth: z.unknown().optional()
22
+ earliest_events: lenient(array(string())),
23
+ latest_events: lenient(array(string())),
24
+ limit: unknown().optional(),
25
+ min_depth: unknown().optional()
24
26
  });
25
27
  const userKeysQueryBody = body({ device_keys: objectMap });
26
28
  const userKeysClaimBody = body({ one_time_keys: objectMap });
27
29
  const publicRoomsBody = body({
28
- limit: z.unknown().optional(),
29
- filter: lenient(z.looseObject({ generic_search_term: lenient(z.string()) }))
30
+ limit: unknown().optional(),
31
+ filter: lenient(looseObject({ generic_search_term: lenient(string()) }))
30
32
  });
31
33
  const F = "/_matrix/federation";
32
34
  /** Authenticate a server-server request; returns the origin server name. */
@@ -66,12 +68,12 @@ async function checkAcl(hs, roomId, origin) {
66
68
  /** Whether `server` may see an event, per history visibility. */
67
69
  async function serverCanSee(hs, server, ev) {
68
70
  const ids = await hs.rooms.snapshotIds(ev.snapshot);
69
- const hvId = ids[stateKey("m.room.history_visibility", "")];
71
+ const hvId = ids[stateKey(M.ROOM.HISTORY_VISIBILITY, "")];
70
72
  const hv = hvId ? (await hs.rooms.getPdu(hvId))?.content?.history_visibility ?? "shared" : "shared";
71
73
  if (hv === "world_readable" || hv === "shared") return true;
72
74
  for (const [key, id] of Object.entries(ids)) {
73
75
  const [type, userId] = splitStateKey(key);
74
- if (type !== "m.room.member" || domainOf(userId) !== server) continue;
76
+ if (type !== M.ROOM.MEMBER || domainOf(userId) !== server) continue;
75
77
  const m = (await hs.rooms.getPdu(id))?.content?.membership;
76
78
  if (m === "join" || hv === "invited" && m === "invite") return true;
77
79
  }
@@ -119,7 +121,7 @@ async function makeMembership(hs, roomId, version, userId, membership, content =
119
121
  const prevs = [...(await hs.rooms.getPdus(extremities)).values()].sort((a, b) => b.depth - a.depth).slice(0, 10);
120
122
  const f = roomVersionFeatures(version);
121
123
  const draft = {
122
- type: "m.room.member",
124
+ type: M.ROOM.MEMBER,
123
125
  sender: userId,
124
126
  state_key: userId,
125
127
  content: {
@@ -131,7 +133,7 @@ async function makeMembership(hs, roomId, version, userId, membership, content =
131
133
  const ref = (id, pdu) => f.legacyEventIds ? [id, pdu?.hashes ?? {}] : id;
132
134
  const authPdus = await hs.rooms.getPdus(authIds);
133
135
  const event = {
134
- type: "m.room.member",
136
+ type: M.ROOM.MEMBER,
135
137
  room_id: roomId,
136
138
  sender: userId,
137
139
  state_key: userId,
@@ -152,7 +154,7 @@ async function makeMembership(hs, roomId, version, userId, membership, content =
152
154
  }
153
155
  /** For restricted rooms: a local user who can authorise a remote join, or why not. */
154
156
  async function restrictedAuthoriser(hs, state, version, userId) {
155
- const joinRules = getState(state, "m.room.join_rules")?.content;
157
+ const joinRules = getState(state, M.ROOM.JOIN_RULES)?.content;
156
158
  const rule = joinRules?.join_rule;
157
159
  const f = roomVersionFeatures(version);
158
160
  if (!(rule === "restricted" && f.restricted || rule === "knock_restricted" && f.knockRestricted)) return null;
@@ -161,7 +163,7 @@ async function restrictedAuthoriser(hs, state, version, userId) {
161
163
  let allowed = false;
162
164
  let unverifiable = false;
163
165
  for (const allow of Array.isArray(joinRules?.allow) ? joinRules.allow : []) {
164
- if (allow?.type !== "m.room_membership" || typeof allow.room_id !== "string") continue;
166
+ if (allow?.type !== M.ROOM_MEMBERSHIP || typeof allow.room_id !== "string") continue;
165
167
  if (!await hs.rooms.serverIsJoined(allow.room_id)) {
166
168
  unverifiable = true;
167
169
  continue;
@@ -178,7 +180,7 @@ async function restrictedAuthoriser(hs, state, version, userId) {
178
180
  const pl = powerLevels(state, version);
179
181
  for (const [key, ev] of state) {
180
182
  const [type, member] = splitStateKey(key);
181
- if (type === "m.room.member" && hs.isLocalUser(member) && ev.content?.membership === "join" && userLevel(pl, member) >= pl.invite) return member;
183
+ if (type === M.ROOM.MEMBER && hs.isLocalUser(member) && ev.content?.membership === "join" && userLevel(pl, member) >= pl.invite) return member;
182
184
  }
183
185
  throw new MatrixError(400, "M_UNABLE_TO_GRANT_JOIN", "No local user can authorise this join");
184
186
  }
@@ -187,7 +189,7 @@ async function restrictedAuthoriser(hs, state, version, userId) {
187
189
  async function exchangeThirdPartyInvite(hs, roomId, sender, invitee, signed) {
188
190
  const token = signed.token;
189
191
  if (typeof token !== "string") throw errors.badJson("signed.token is required");
190
- const pending = getState(await hs.rooms.currentState(roomId), "m.room.third_party_invite", token);
192
+ const pending = getState(await hs.rooms.currentState(roomId), M.ROOM.THIRD_PARTY_INVITE, token);
191
193
  if (!pending) throw errors.forbidden("No such third-party invite");
192
194
  if (pending.sender !== sender) throw errors.forbidden("The third-party invite was sent by someone else");
193
195
  await hs.rooms.invite(roomId, sender, invitee, { thirdPartyInvite: {
@@ -201,7 +203,7 @@ async function sendMembership(ctx, origin, membership) {
201
203
  const version = await roomOr404(hs, roomId);
202
204
  await checkAcl(hs, roomId, origin);
203
205
  const raw = await ctx.json();
204
- if (raw.type !== "m.room.member" || raw.content?.membership !== membership || raw.state_key !== raw.sender) throw errors.badJson(`Expected a ${membership} event`);
206
+ if (raw.type !== M.ROOM.MEMBER || raw.content?.membership !== membership || raw.state_key !== raw.sender) throw errors.badJson(`Expected a ${membership} event`);
205
207
  if (domainOf(raw.sender) !== origin) throw errors.forbidden("Sender does not belong to the origin server");
206
208
  let { pdu, joinAuthorisedSignatureValid } = await hs.federation.verifyPdu(raw, version, roomId);
207
209
  if (pdu.event_id !== ctx.params.eventId) throw errors.badJson("Event ID mismatch");
@@ -235,8 +237,12 @@ async function sendMembership(ctx, origin, membership) {
235
237
  };
236
238
  }
237
239
  function registerFederationRoutes(r) {
238
- const fed = (method, path, handler, options = {}) => {
239
- r.add(method, `${F}${path}`, async (ctx) => handler(ctx, await authenticate(ctx)), options);
240
+ const fed = (method, path, def) => {
241
+ r.add(method, `${F}${path}`, procedure({
242
+ input: def.input,
243
+ guard: async (ctx) => ({ origin: await authenticate(ctx) }),
244
+ handler: (ctx) => def.handler(ctx, ctx.origin)
245
+ }));
240
246
  };
241
247
  const notary = async (ctx, servers) => {
242
248
  const out = [];
@@ -262,16 +268,26 @@ function registerFederationRoutes(r) {
262
268
  }
263
269
  return { server_keys: out };
264
270
  };
265
- r.post("/_matrix/key/v2/query", { input: keyQueryBody }, async (ctx) => {
266
- return canonicalJsonResponse(await notary(ctx, Object.keys(ctx.input.server_keys ?? {})));
267
- });
268
- r.get("/_matrix/key/v2/query/:serverName", async (ctx) => canonicalJsonResponse(await notary(ctx, [ctx.params.serverName])));
269
- fed("PUT", "/v1/send/:txnId", async (ctx, origin) => {
271
+ const requireFederation = (ctx) => {
272
+ if (!ctx.hs.federation.enabled) throw errors.unrecognized("Federation is disabled");
273
+ };
274
+ r.post("/_matrix/key/v2/query", procedure({
275
+ input: keyQueryBody,
276
+ handler: async (ctx) => {
277
+ requireFederation(ctx);
278
+ return canonicalJsonResponse(await notary(ctx, Object.keys(ctx.input.server_keys ?? {})));
279
+ }
280
+ }));
281
+ r.get("/_matrix/key/v2/query/:serverName", procedure({ handler: async (ctx) => {
282
+ requireFederation(ctx);
283
+ return canonicalJsonResponse(await notary(ctx, [ctx.params.serverName]));
284
+ } }));
285
+ fed("PUT", "/v1/send/:txnId", { handler: async (ctx, origin) => {
270
286
  const body = await ctx.json();
271
287
  if (body.origin !== void 0 && body.origin !== origin) throw errors.forbidden("Transaction origin mismatch");
272
288
  return ctx.hs.federation.receiveTransaction(origin, ctx.params.txnId, body);
273
- });
274
- fed("GET", "/v1/make_join/:roomId/:userId", async (ctx, origin) => {
289
+ } });
290
+ fed("GET", "/v1/make_join/:roomId/:userId", { handler: async (ctx, origin) => {
275
291
  const hs = ctx.hs;
276
292
  const { roomId, userId } = ctx.params;
277
293
  if (domainOf(userId) !== origin) throw errors.forbidden("User does not belong to the origin server");
@@ -286,7 +302,7 @@ function registerFederationRoutes(r) {
286
302
  event: await makeMembership(hs, roomId, version, userId, "join", via ? { join_authorised_via_users_server: via } : {}),
287
303
  room_version: version
288
304
  };
289
- });
305
+ } });
290
306
  const sendJoin = async (ctx, origin) => {
291
307
  const hs = ctx.hs;
292
308
  await rejectIfPartial(hs, ctx.params.roomId);
@@ -304,9 +320,9 @@ function registerFederationRoutes(r) {
304
320
  servers_in_room: servers
305
321
  };
306
322
  };
307
- fed("PUT", "/v2/send_join/:roomId/:eventId", sendJoin);
308
- fed("PUT", "/v1/send_join/:roomId/:eventId", async (ctx, origin) => [200, await sendJoin(ctx, origin)]);
309
- fed("GET", "/v1/make_leave/:roomId/:userId", async (ctx, origin) => {
323
+ fed("PUT", "/v2/send_join/:roomId/:eventId", { handler: sendJoin });
324
+ fed("PUT", "/v1/send_join/:roomId/:eventId", { handler: async (ctx, origin) => [200, await sendJoin(ctx, origin)] });
325
+ fed("GET", "/v1/make_leave/:roomId/:userId", { handler: async (ctx, origin) => {
310
326
  const { roomId, userId } = ctx.params;
311
327
  if (domainOf(userId) !== origin) throw errors.forbidden("User does not belong to the origin server");
312
328
  const version = await roomOr404(ctx.hs, roomId);
@@ -315,16 +331,16 @@ function registerFederationRoutes(r) {
315
331
  event: await makeMembership(ctx.hs, roomId, version, userId, "leave"),
316
332
  room_version: version
317
333
  };
318
- });
319
- fed("PUT", "/v2/send_leave/:roomId/:eventId", async (ctx, origin) => {
334
+ } });
335
+ fed("PUT", "/v2/send_leave/:roomId/:eventId", { handler: async (ctx, origin) => {
320
336
  await sendMembership(ctx, origin, "leave");
321
337
  return {};
322
- });
323
- fed("PUT", "/v1/send_leave/:roomId/:eventId", async (ctx, origin) => {
338
+ } });
339
+ fed("PUT", "/v1/send_leave/:roomId/:eventId", { handler: async (ctx, origin) => {
324
340
  await sendMembership(ctx, origin, "leave");
325
341
  return [200, {}];
326
- });
327
- fed("GET", "/v1/make_knock/:roomId/:userId", async (ctx, origin) => {
342
+ } });
343
+ fed("GET", "/v1/make_knock/:roomId/:userId", { handler: async (ctx, origin) => {
328
344
  const { roomId, userId } = ctx.params;
329
345
  if (domainOf(userId) !== origin) throw errors.forbidden("User does not belong to the origin server");
330
346
  const version = await roomOr404(ctx.hs, roomId);
@@ -336,19 +352,19 @@ function registerFederationRoutes(r) {
336
352
  event: await makeMembership(ctx.hs, roomId, version, userId, "knock"),
337
353
  room_version: version
338
354
  };
339
- });
340
- fed("PUT", "/v1/send_knock/:roomId/:eventId", async (ctx, origin) => {
355
+ } });
356
+ fed("PUT", "/v1/send_knock/:roomId/:eventId", { handler: async (ctx, origin) => {
341
357
  await rejectIfPartial(ctx.hs, ctx.params.roomId);
342
358
  await sendMembership(ctx, origin, "knock");
343
359
  return { knock_room_state: ctx.hs.rooms.strippedState(await ctx.hs.rooms.currentState(ctx.params.roomId)) };
344
- });
360
+ } });
345
361
  const invite = async (ctx, origin, v2) => {
346
362
  const hs = ctx.hs;
347
363
  const body = await ctx.json();
348
364
  const raw = v2 ? body.event : body;
349
365
  const version = v2 ? String(body.room_version ?? "1") : "1";
350
366
  if (!SUPPORTED_ROOM_VERSIONS.includes(version)) throw new MatrixError(400, "M_INCOMPATIBLE_ROOM_VERSION", "Unsupported room version", { room_version: version });
351
- if (!isObject(raw) || raw.type !== "m.room.member" || raw.content?.membership !== "invite" || typeof raw.state_key !== "string") throw errors.badJson("Expected an invite event");
367
+ if (!isObject(raw) || raw.type !== M.ROOM.MEMBER || raw.content?.membership !== "invite" || typeof raw.state_key !== "string") throw errors.badJson("Expected an invite event");
352
368
  if (raw.room_id !== ctx.params.roomId) throw errors.badJson("Room ID mismatch");
353
369
  if (domainOf(raw.sender) !== origin) throw errors.forbidden("Sender does not belong to the origin server");
354
370
  const target = raw.state_key;
@@ -373,7 +389,7 @@ function registerFederationRoutes(r) {
373
389
  },
374
390
  {
375
391
  field: "type",
376
- value: "m.ignored_user_list"
392
+ value: M.IGNORED_USER_LIST
377
393
  }
378
394
  ]);
379
395
  const signedRedacted = signJson(redact({ ...raw }, version), hs.serverName, hs.signingKey);
@@ -382,7 +398,7 @@ function registerFederationRoutes(r) {
382
398
  [hs.serverName]: signedRedacted.signatures[hs.serverName]
383
399
  };
384
400
  const inviteState = Array.isArray(body.invite_room_state) ? body.invite_room_state : Array.isArray(raw.unsigned?.invite_room_state) ? raw.unsigned.invite_room_state : [];
385
- const createInState = inviteState.find((e) => e?.type === "m.room.create");
401
+ const createInState = inviteState.find((e) => e?.type === M.ROOM.CREATE);
386
402
  if (v2 && !createInState && roomVersionFeatures(version).v12) throw errors.missingParam("invite_room_state must include the m.room.create event");
387
403
  if (createInState && roomVersionFeatures(version).v12 && createInState.event_id === void 0) try {
388
404
  if (hs.federation.computeEventId(createInState, version) !== `$${raw.room_id.slice(1)}`) throw new Error("mismatch");
@@ -404,9 +420,9 @@ function registerFederationRoutes(r) {
404
420
  }, version);
405
421
  return v2 ? { event: wire } : [200, { event: wire }];
406
422
  };
407
- fed("PUT", "/v2/invite/:roomId/:eventId", (ctx, origin) => invite(ctx, origin, true));
408
- fed("PUT", "/v1/invite/:roomId/:eventId", (ctx, origin) => invite(ctx, origin, false));
409
- fed("GET", "/v1/event/:eventId", async (ctx, origin) => {
423
+ fed("PUT", "/v2/invite/:roomId/:eventId", { handler: (ctx, origin) => invite(ctx, origin, true) });
424
+ fed("PUT", "/v1/invite/:roomId/:eventId", { handler: (ctx, origin) => invite(ctx, origin, false) });
425
+ fed("GET", "/v1/event/:eventId", { handler: async (ctx, origin) => {
410
426
  const hs = ctx.hs;
411
427
  const ev = await hs.rooms.getEvent(ctx.params.eventId);
412
428
  const pdu = ev?.pdu ?? await hs.rooms.getPdu(ctx.params.eventId);
@@ -419,7 +435,7 @@ function registerFederationRoutes(r) {
419
435
  origin_server_ts: Date.now(),
420
436
  pdus: [hs.federation.toFederationPdu(pdu, version)]
421
437
  };
422
- });
438
+ } });
423
439
  const stateHandler = (idsOnly) => async (ctx, origin) => {
424
440
  const hs = ctx.hs;
425
441
  const roomId = ctx.params.roomId;
@@ -440,15 +456,15 @@ function registerFederationRoutes(r) {
440
456
  auth_chain: chain.map((p) => hs.federation.toFederationPdu(p, version))
441
457
  };
442
458
  };
443
- fed("GET", "/v1/state/:roomId", stateHandler(false));
444
- fed("GET", "/v1/state_ids/:roomId", stateHandler(true));
445
- fed("GET", "/v1/event_auth/:roomId/:eventId", async (ctx, origin) => {
459
+ fed("GET", "/v1/state/:roomId", { handler: stateHandler(false) });
460
+ fed("GET", "/v1/state_ids/:roomId", { handler: stateHandler(true) });
461
+ fed("GET", "/v1/event_auth/:roomId/:eventId", { handler: async (ctx, origin) => {
446
462
  const hs = ctx.hs;
447
463
  const version = await roomOr404(hs, ctx.params.roomId);
448
464
  await checkAcl(hs, ctx.params.roomId, origin);
449
465
  return { auth_chain: (await authChainOf(hs, [ctx.params.eventId])).map((p) => hs.federation.toFederationPdu(p, version)) };
450
- });
451
- fed("GET", "/v1/backfill/:roomId", async (ctx, origin) => {
466
+ } });
467
+ fed("GET", "/v1/backfill/:roomId", { handler: async (ctx, origin) => {
452
468
  const hs = ctx.hs;
453
469
  const roomId = ctx.params.roomId;
454
470
  const version = await roomOr404(hs, roomId);
@@ -475,40 +491,43 @@ function registerFederationRoutes(r) {
475
491
  origin_server_ts: Date.now(),
476
492
  pdus: out.map((p) => hs.federation.toFederationPdu(p, version))
477
493
  };
478
- });
479
- fed("POST", "/v1/get_missing_events/:roomId", async (ctx, origin) => {
480
- const hs = ctx.hs;
481
- const roomId = ctx.params.roomId;
482
- const version = await roomOr404(hs, roomId);
483
- const state = await checkAcl(hs, roomId, origin);
484
- if (!FederationJoinGuard.serverInRoom(state, origin)) throw errors.forbidden("Your server is not in the room");
485
- const body = ctx.input;
486
- const earliest = new Set(body.earliest_events ?? []);
487
- const limit = Math.min(Number(body.limit ?? 10) || 10, 100);
488
- const minDepth = Number(body.min_depth ?? 0) || 0;
489
- const out = [];
490
- const seen = new Set(body.latest_events ?? []);
491
- let frontier = [];
492
- for (const id of seen) {
493
- const ev = await hs.rooms.getEvent(id);
494
- if (ev) frontier.push(...(ev.pdu.prev_events ?? []).map(eventIdOf));
495
- }
496
- while (frontier.length && out.length < limit) {
497
- const next = [];
498
- for (const id of frontier) {
499
- if (seen.has(id) || earliest.has(id) || out.length >= limit) continue;
500
- seen.add(id);
494
+ } });
495
+ fed("POST", "/v1/get_missing_events/:roomId", {
496
+ input: missingEventsBody,
497
+ handler: async (ctx, origin) => {
498
+ const hs = ctx.hs;
499
+ const roomId = ctx.params.roomId;
500
+ const version = await roomOr404(hs, roomId);
501
+ const state = await checkAcl(hs, roomId, origin);
502
+ if (!FederationJoinGuard.serverInRoom(state, origin)) throw errors.forbidden("Your server is not in the room");
503
+ const body = ctx.input;
504
+ const earliest = new Set(body.earliest_events ?? []);
505
+ const limit = Math.min(Number(body.limit ?? 10) || 10, 100);
506
+ const minDepth = Number(body.min_depth ?? 0) || 0;
507
+ const out = [];
508
+ const seen = new Set(body.latest_events ?? []);
509
+ let frontier = [];
510
+ for (const id of seen) {
501
511
  const ev = await hs.rooms.getEvent(id);
502
- if (!ev || ev.pdu.depth < minDepth) continue;
503
- out.push(await serverCanSee(hs, origin, ev) ? ev.pdu : redact(ev.pdu, version));
504
- next.push(...(ev.pdu.prev_events ?? []).map(eventIdOf));
512
+ if (ev) frontier.push(...(ev.pdu.prev_events ?? []).map(eventIdOf));
505
513
  }
506
- frontier = next;
514
+ while (frontier.length && out.length < limit) {
515
+ const next = [];
516
+ for (const id of frontier) {
517
+ if (seen.has(id) || earliest.has(id) || out.length >= limit) continue;
518
+ seen.add(id);
519
+ const ev = await hs.rooms.getEvent(id);
520
+ if (!ev || ev.pdu.depth < minDepth) continue;
521
+ out.push(await serverCanSee(hs, origin, ev) ? ev.pdu : redact(ev.pdu, version));
522
+ next.push(...(ev.pdu.prev_events ?? []).map(eventIdOf));
523
+ }
524
+ frontier = next;
525
+ }
526
+ out.sort((a, b) => a.depth - b.depth);
527
+ return { events: out.map((p) => hs.federation.toFederationPdu(p, version)) };
507
528
  }
508
- out.sort((a, b) => a.depth - b.depth);
509
- return { events: out.map((p) => hs.federation.toFederationPdu(p, version)) };
510
- }, { input: missingEventsBody });
511
- fed("GET", "/v1/timestamp_to_event/:roomId", async (ctx, origin) => {
529
+ });
530
+ fed("GET", "/v1/timestamp_to_event/:roomId", { handler: async (ctx, origin) => {
512
531
  const hs = ctx.hs;
513
532
  const roomId = ctx.params.roomId;
514
533
  await roomOr404(hs, roomId);
@@ -534,8 +553,8 @@ function registerFederationRoutes(r) {
534
553
  };
535
554
  }
536
555
  throw errors.notFound("No event found");
537
- });
538
- fed("GET", "/v1/query/directory", async (ctx) => {
556
+ } });
557
+ fed("GET", "/v1/query/directory", { handler: async (ctx) => {
539
558
  const alias = ctx.query.get("room_alias");
540
559
  if (!alias) throw errors.missingParam("Missing room_alias");
541
560
  const row = await ctx.hs.db.findOne("room_aliases", [{
@@ -548,8 +567,8 @@ function registerFederationRoutes(r) {
548
567
  room_id: row.room_id,
549
568
  servers: [...new Set(servers)]
550
569
  };
551
- });
552
- fed("GET", "/v1/query/profile", async (ctx) => {
570
+ } });
571
+ fed("GET", "/v1/query/profile", { handler: async (ctx) => {
553
572
  const userId = ctx.query.get("user_id");
554
573
  if (!userId) throw errors.missingParam("Missing user_id");
555
574
  if (!isValidUserId(userId)) throw errors.invalidParam("Invalid user ID");
@@ -560,24 +579,33 @@ function registerFederationRoutes(r) {
560
579
  if (!field) return profile;
561
580
  if (profile[field] === void 0) throw errors.notFound("Profile field not set");
562
581
  return { [field]: profile[field] };
582
+ } });
583
+ fed("POST", "/unstable/event_relationships", {
584
+ input: body({}),
585
+ handler: async (ctx, origin) => relationshipsForServer(ctx.hs, origin, ctx.input, (ids) => authChainOf(ctx.hs, ids))
563
586
  });
564
- fed("POST", "/unstable/event_relationships", async (ctx, origin) => relationshipsForServer(ctx.hs, origin, ctx.input, (ids) => authChainOf(ctx.hs, ids)), { input: body({}) });
565
- fed("POST", "/v1/user/keys/query", async (ctx) => {
566
- const local = {};
567
- for (const [userId, devices] of Object.entries(ctx.input.device_keys ?? {})) if (ctx.hs.isLocalUser(userId)) local[userId] = Array.isArray(devices) ? devices : [];
568
- const res = await ctx.hs.keys.query("", local);
569
- return {
570
- device_keys: res.device_keys,
571
- master_keys: res.master_keys,
572
- self_signing_keys: res.self_signing_keys
573
- };
574
- }, { input: userKeysQueryBody });
575
- fed("POST", "/v1/user/keys/claim", async (ctx) => {
576
- const local = {};
577
- for (const [userId, devices] of Object.entries(ctx.input.one_time_keys ?? {})) if (ctx.hs.isLocalUser(userId) && isObject(devices)) local[userId] = devices;
578
- return { one_time_keys: await ctx.hs.keys.claim(local) };
579
- }, { input: userKeysClaimBody });
580
- fed("GET", "/v1/user/devices/:userId", async (ctx) => {
587
+ fed("POST", "/v1/user/keys/query", {
588
+ input: userKeysQueryBody,
589
+ handler: async (ctx) => {
590
+ const local = {};
591
+ for (const [userId, devices] of Object.entries(ctx.input.device_keys ?? {})) if (ctx.hs.isLocalUser(userId)) local[userId] = Array.isArray(devices) ? devices : [];
592
+ const res = await ctx.hs.keys.query("", local);
593
+ return {
594
+ device_keys: res.device_keys,
595
+ master_keys: res.master_keys,
596
+ self_signing_keys: res.self_signing_keys
597
+ };
598
+ }
599
+ });
600
+ fed("POST", "/v1/user/keys/claim", {
601
+ input: userKeysClaimBody,
602
+ handler: async (ctx) => {
603
+ const local = {};
604
+ for (const [userId, devices] of Object.entries(ctx.input.one_time_keys ?? {})) if (ctx.hs.isLocalUser(userId) && isObject(devices)) local[userId] = devices;
605
+ return { one_time_keys: await ctx.hs.keys.claim(local) };
606
+ }
607
+ });
608
+ fed("GET", "/v1/user/devices/:userId", { handler: async (ctx) => {
581
609
  const hs = ctx.hs;
582
610
  const userId = ctx.params.userId;
583
611
  if (!hs.isLocalUser(userId)) throw errors.forbidden("User is not local");
@@ -601,7 +629,7 @@ function registerFederationRoutes(r) {
601
629
  if (keys.master_keys[userId]) out.master_key = keys.master_keys[userId];
602
630
  if (keys.self_signing_keys[userId]) out.self_signing_key = keys.self_signing_keys[userId];
603
631
  return out;
604
- });
632
+ } });
605
633
  const publicRooms = async (ctx) => {
606
634
  const hs = ctx.hs;
607
635
  const body = (ctx.method === "POST" ? ctx.input : void 0) ?? {};
@@ -626,9 +654,12 @@ function registerFederationRoutes(r) {
626
654
  if (limit && since + limit < chunk.length) out.next_batch = `o${since + limit}`;
627
655
  return out;
628
656
  };
629
- fed("GET", "/v1/publicRooms", (ctx) => publicRooms(ctx));
630
- fed("POST", "/v1/publicRooms", (ctx) => publicRooms(ctx), { input: publicRoomsBody });
631
- fed("GET", "/v1/hierarchy/:roomId", async (ctx, origin) => {
657
+ fed("GET", "/v1/publicRooms", { handler: (ctx) => publicRooms(ctx) });
658
+ fed("POST", "/v1/publicRooms", {
659
+ input: publicRoomsBody,
660
+ handler: (ctx) => publicRooms(ctx)
661
+ });
662
+ fed("GET", "/v1/hierarchy/:roomId", { handler: async (ctx, origin) => {
632
663
  const hs = ctx.hs;
633
664
  const roomId = ctx.params.roomId;
634
665
  await roomOr404(hs, roomId);
@@ -636,14 +667,14 @@ function registerFederationRoutes(r) {
636
667
  const suggestedOnly = ctx.query.get("suggested_only") === "true";
637
668
  const summary = async (id, s) => {
638
669
  const details = await roomDetails(hs, id);
639
- const jr = getState(s, "m.room.join_rules")?.content;
640
- if (["restricted", "knock_restricted"].includes(jr?.join_rule)) details.allowed_room_ids = (Array.isArray(jr?.allow) ? jr.allow : []).filter((a) => a?.type === "m.room_membership").map((a) => a.room_id);
670
+ const jr = getState(s, M.ROOM.JOIN_RULES)?.content;
671
+ if (["restricted", "knock_restricted"].includes(jr?.join_rule)) details.allowed_room_ids = (Array.isArray(jr?.allow) ? jr.allow : []).filter((a) => a?.type === M.ROOM_MEMBERSHIP).map((a) => a.room_id);
641
672
  return details;
642
673
  };
643
674
  const accessible = async (s) => {
644
- const jr = getState(s, "m.room.join_rules")?.content;
675
+ const jr = getState(s, M.ROOM.JOIN_RULES)?.content;
645
676
  const rule = jr?.join_rule ?? "invite";
646
- const hv = getState(s, "m.room.history_visibility")?.content?.history_visibility;
677
+ const hv = getState(s, M.ROOM.HISTORY_VISIBILITY)?.content?.history_visibility;
647
678
  if ([
648
679
  "public",
649
680
  "knock",
@@ -651,7 +682,7 @@ function registerFederationRoutes(r) {
651
682
  ].includes(rule) || hv === "world_readable" || FederationJoinGuard.serverInRoom(s, origin)) return true;
652
683
  if (rule !== "restricted") return false;
653
684
  for (const allow of Array.isArray(jr?.allow) ? jr.allow : []) {
654
- if (allow?.type !== "m.room_membership" || typeof allow.room_id !== "string" || !await hs.rooms.exists(allow.room_id)) continue;
685
+ if (allow?.type !== M.ROOM_MEMBERSHIP || typeof allow.room_id !== "string" || !await hs.rooms.exists(allow.room_id)) continue;
655
686
  if (FederationJoinGuard.serverInRoom(await hs.rooms.currentState(allow.room_id), origin)) return true;
656
687
  }
657
688
  return false;
@@ -662,7 +693,7 @@ function registerFederationRoutes(r) {
662
693
  const inaccessible = [];
663
694
  for (const [key, ev] of state) {
664
695
  const [type, childId] = splitStateKey(key);
665
- if (type !== "m.space.child" || !Array.isArray(ev.content?.via) || !ev.content.via.length) continue;
696
+ if (type !== M.SPACE.CHILD || !Array.isArray(ev.content?.via) || !ev.content.via.length) continue;
666
697
  if (suggestedOnly && ev.content.suggested !== true) continue;
667
698
  childrenState.push({
668
699
  type,
@@ -688,7 +719,7 @@ function registerFederationRoutes(r) {
688
719
  children,
689
720
  inaccessible_children: inaccessible
690
721
  };
691
- });
722
+ } });
692
723
  const mediaHandler = async (ctx) => {
693
724
  const hs = ctx.hs;
694
725
  const meta = await hs.db.findOne("media", [{
@@ -710,40 +741,43 @@ function registerFederationRoutes(r) {
710
741
  headers: { "content-type": `multipart/mixed; boundary=${boundary}` }
711
742
  });
712
743
  };
713
- fed("GET", "/v1/media/download/:mediaId", (ctx) => mediaHandler(ctx));
714
- fed("GET", "/v1/media/thumbnail/:mediaId", (ctx) => mediaHandler(ctx));
715
- r.get(`${F}/v1/openid/userinfo`, async (ctx) => {
744
+ fed("GET", "/v1/media/download/:mediaId", { handler: (ctx) => mediaHandler(ctx) });
745
+ fed("GET", "/v1/media/thumbnail/:mediaId", { handler: (ctx) => mediaHandler(ctx) });
746
+ r.get(`${F}/v1/openid/userinfo`, procedure({ handler: async (ctx) => {
716
747
  const token = ctx.query.get("access_token");
717
748
  const userId = token ? await ctx.hs.cluster.get(`openid:${token}`) : null;
718
749
  if (!userId) throw new MatrixError(401, "M_UNKNOWN_TOKEN", "Invalid OpenID token");
719
750
  return { sub: userId };
720
- });
721
- r.client("POST", "/user/:userId/openid/request_token", async (ctx) => {
722
- const s = await ctx.auth();
723
- if (ctx.params.userId !== s.userId) throw errors.forbidden("Cannot request tokens for other users");
724
- const token = randomToken();
725
- await ctx.hs.cluster.set(`openid:${token}`, s.userId, 36e5);
726
- return {
727
- access_token: token,
728
- token_type: "Bearer",
729
- matrix_server_name: ctx.hs.serverName,
730
- expires_in: 3600
731
- };
732
- });
733
- fed("PUT", "/v1/exchange_third_party_invite/:roomId", async (ctx, origin) => {
751
+ } }));
752
+ r.client("POST", "/user/:userId/openid/request_token", procedure({
753
+ auth: "user",
754
+ handler: async (ctx) => {
755
+ const s = ctx.session;
756
+ if (ctx.params.userId !== s.userId) throw errors.forbidden("Cannot request tokens for other users");
757
+ const token = randomToken();
758
+ await ctx.hs.cluster.set(`openid:${token}`, s.userId, 36e5);
759
+ return {
760
+ access_token: token,
761
+ token_type: "Bearer",
762
+ matrix_server_name: ctx.hs.serverName,
763
+ expires_in: 3600
764
+ };
765
+ }
766
+ }));
767
+ fed("PUT", "/v1/exchange_third_party_invite/:roomId", { handler: async (ctx, origin) => {
734
768
  const hs = ctx.hs;
735
769
  const roomId = ctx.params.roomId;
736
770
  const event = await ctx.json();
737
771
  const signed = event.content?.third_party_invite?.signed;
738
- if (event.type !== "m.room.member" || event.room_id !== roomId || event.content?.membership !== "invite" || !isObject(signed)) throw errors.badJson("Expected an m.room.member invite with third_party_invite");
772
+ if (event.type !== M.ROOM.MEMBER || event.room_id !== roomId || event.content?.membership !== "invite" || !isObject(signed)) throw errors.badJson("Expected an m.room.member invite with third_party_invite");
739
773
  if (typeof event.sender !== "string" || !hs.isLocalUser(event.sender)) throw errors.forbidden("The sender is not a user of this server");
740
774
  if (typeof event.state_key !== "string" || domainOf(event.state_key) !== origin || signed.mxid !== event.state_key) throw errors.forbidden("The invitee does not belong to the requesting server");
741
775
  await roomOr404(hs, roomId);
742
776
  await checkAcl(hs, roomId, origin);
743
777
  await exchangeThirdPartyInvite(hs, roomId, event.sender, event.state_key, signed);
744
778
  return {};
745
- });
746
- r.put(`${F}/v1/3pid/onbind`, async (ctx) => {
779
+ } });
780
+ r.put(`${F}/v1/3pid/onbind`, procedure({ handler: async (ctx) => {
747
781
  const hs = ctx.hs;
748
782
  const body = await ctx.json();
749
783
  const invites = Array.isArray(body.invites) ? body.invites : [];
@@ -752,7 +786,7 @@ function registerFederationRoutes(r) {
752
786
  if (!isObject(signed) || typeof signed.mxid !== "string" || !hs.isLocalUser(signed.mxid)) continue;
753
787
  if (typeof invite.room_id !== "string" || typeof invite.sender !== "string") continue;
754
788
  const event = {
755
- type: "m.room.member",
789
+ type: M.ROOM.MEMBER,
756
790
  room_id: invite.room_id,
757
791
  sender: invite.sender,
758
792
  state_key: signed.mxid,
@@ -769,21 +803,21 @@ function registerFederationRoutes(r) {
769
803
  }
770
804
  }
771
805
  return {};
772
- });
806
+ } }));
773
807
  }
774
808
  /** Helpers for "is a server in the room" checks. */
775
809
  const FederationJoinGuard = {
776
810
  serverInRoom(state, server) {
777
811
  for (const [key, ev] of state) {
778
812
  const [type, userId] = splitStateKey(key);
779
- if (type === "m.room.member" && domainOf(userId) === server && ev.content?.membership === "join") return true;
813
+ if (type === M.ROOM.MEMBER && domainOf(userId) === server && ev.content?.membership === "join") return true;
780
814
  }
781
815
  return false;
782
816
  },
783
817
  noLocalMembers(hs, state) {
784
818
  for (const [key, ev] of state) {
785
819
  const [type, userId] = splitStateKey(key);
786
- if (type === "m.room.member" && hs.isLocalUser(userId) && ev.content?.membership === "join") return false;
820
+ if (type === M.ROOM.MEMBER && hs.isLocalUser(userId) && ev.content?.membership === "join") return false;
787
821
  }
788
822
  return true;
789
823
  }
@@ -1,4 +1,5 @@
1
1
  import { isObject } from "../core/json.js";
2
+ import { M, UNSTABLE } from "../core/m.js";
2
3
  import { errors } from "../core/errors.js";
3
4
  import { globMatch as globMatch$1 } from "../core/glob.js";
4
5
  //#region src/api/filters.ts
@@ -17,10 +18,10 @@ function matchesFilter(filter, ev) {
17
18
  if (filter.not_rooms?.includes(ev.room_id)) return false;
18
19
  if (filter.rooms && !filter.rooms.includes(ev.room_id)) return false;
19
20
  }
20
- const relTypes = filter.rel_types ?? filter["org.matrix.msc3874.rel_types"];
21
- const notRelTypes = filter.not_rel_types ?? filter["org.matrix.msc3874.not_rel_types"];
21
+ const relTypes = filter.rel_types ?? filter[UNSTABLE.ORG.MATRIX.MSC3874.REL_TYPES];
22
+ const notRelTypes = filter.not_rel_types ?? filter[UNSTABLE.ORG.MATRIX.MSC3874.NOT_REL_TYPES];
22
23
  if (relTypes || notRelTypes) {
23
- const relType = ev.content?.["m.relates_to"]?.rel_type;
24
+ const relType = ev.content?.[M.RELATES_TO]?.rel_type;
24
25
  if (relTypes && !relTypes.includes(relType)) return false;
25
26
  if (notRelTypes && typeof relType === "string" && notRelTypes.includes(relType)) return false;
26
27
  }