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,4 +1,5 @@
1
1
  import { isObject } from "./json.js";
2
+ import { M, UNSTABLE } from "./m.js";
2
3
  import { SUPPORTED_ROOM_VERSIONS, eventIdOf, finalisePdu, redact, roomVersionFeatures, splitStateKey, stateKey, toClientEvent } from "./events.js";
3
4
  import { MatrixError, errors } from "./errors.js";
4
5
  import { generateRoomId, isValidUserId, parseId } from "./ids.js";
@@ -280,7 +281,7 @@ var RoomService = class {
280
281
  return state;
281
282
  }
282
283
  async createEventOf(roomId) {
283
- return getState(await this.currentState(roomId), "m.room.create");
284
+ return getState(await this.currentState(roomId), M.ROOM.CREATE);
284
285
  }
285
286
  /**
286
287
  * State before an event with the given prev_events: the snapshot of a single
@@ -384,7 +385,7 @@ var RoomService = class {
384
385
  pdu.auth_events = pdu.auth_events.map((id) => [id, {}]);
385
386
  }
386
387
  if (!skipAuth) {
387
- if (features.v12) before.set(stateKey("m.room.create", ""), await this.createEventOf(roomId));
388
+ if (features.v12) before.set(stateKey(M.ROOM.CREATE, ""), await this.createEventOf(roomId));
388
389
  checkAuth(pdu, before, version);
389
390
  }
390
391
  if (Buffer.byteLength(JSON.stringify(pdu)) > MAX_EVENT_SIZE) throw errors.tooLarge("Event too large");
@@ -401,7 +402,7 @@ var RoomService = class {
401
402
  eventId,
402
403
  status: "persisted"
403
404
  };
404
- const createEvent = features.v12 ? pdu.type === "m.room.create" ? void 0 : opts.createEvent ?? await this.createEventOf(roomId) ?? await this.findCreateEvent(roomId) : void 0;
405
+ const createEvent = features.v12 ? pdu.type === M.ROOM.CREATE ? void 0 : opts.createEvent ?? await this.createEventOf(roomId) ?? await this.findCreateEvent(roomId) : void 0;
405
406
  const prevIds = (pdu.prev_events ?? []).map(eventIdOf);
406
407
  if (!opts.local) {
407
408
  const reject = async (reason) => {
@@ -436,7 +437,7 @@ var RoomService = class {
436
437
  };
437
438
  else before = await this.stateBefore(roomId, version, prevIds, createEvent);
438
439
  const beforeMap = await this.stateIdsToMap(before.ids);
439
- if (createEvent) beforeMap.set(stateKey("m.room.create", ""), createEvent);
440
+ if (createEvent) beforeMap.set(stateKey(M.ROOM.CREATE, ""), createEvent);
440
441
  const partial = !opts.local && !opts.backfill && await this.hs.partialState.isPartial(roomId);
441
442
  if (partial) {
442
443
  const authPdus = await this.getPdus((pdu.auth_events ?? []).map(eventIdOf));
@@ -469,7 +470,7 @@ var RoomService = class {
469
470
  }
470
471
  if (!opts.backfill && !opts.stateBefore && !partial) {
471
472
  const current = new Map(await this.currentState(roomId));
472
- if (createEvent) current.set(stateKey("m.room.create", ""), createEvent);
473
+ if (createEvent) current.set(stateKey(M.ROOM.CREATE, ""), createEvent);
473
474
  const softFail = async () => {
474
475
  const ids = pdu.state_key !== void 0 ? {
475
476
  ...before.ids,
@@ -539,7 +540,7 @@ var RoomService = class {
539
540
  }], { state_snapshot: snapshot });
540
541
  this.eventCache.delete(eventId);
541
542
  };
542
- const joinKey = stateKey("m.room.member", join.pdu.state_key);
543
+ const joinKey = stateKey(M.ROOM.MEMBER, join.pdu.state_key);
543
544
  await setSnapshot(joinEventId, await writeSnapshot({
544
545
  ...fullBefore,
545
546
  [joinKey]: joinEventId
@@ -586,7 +587,7 @@ var RoomService = class {
586
587
  continue;
587
588
  }
588
589
  const beforeMap = await this.stateIdsToMap(before.ids);
589
- if (createEvent) beforeMap.set(stateKey("m.room.create", ""), createEvent);
590
+ if (createEvent) beforeMap.set(stateKey(M.ROOM.CREATE, ""), createEvent);
590
591
  let allowed = true;
591
592
  let reason = "";
592
593
  if (!this.hs.isLocalUser(pdu.sender)) try {
@@ -627,7 +628,7 @@ var RoomService = class {
627
628
  touched.push(pdu.event_id);
628
629
  continue;
629
630
  }
630
- if (pdu.type === "m.room.member" && pdu.content?.membership === "join") joinedServers.add(pdu.state_key.slice(pdu.state_key.indexOf(":") + 1));
631
+ if (pdu.type === M.ROOM.MEMBER && pdu.content?.membership === "join") joinedServers.add(pdu.state_key.slice(pdu.state_key.indexOf(":") + 1));
631
632
  const isState = pdu.state_key !== void 0;
632
633
  const snapshot = !isState && before.snapshot !== null && before.snapshot !== 0 ? before.snapshot : await writeSnapshot(isState ? {
633
634
  ...before.ids,
@@ -698,7 +699,7 @@ var RoomService = class {
698
699
  ];
699
700
  if (!newIds[k]) {
700
701
  await db.delete("current_state", where);
701
- if (type === "m.room.member") await db.delete("memberships", [{
702
+ if (type === M.ROOM.MEMBER) await db.delete("memberships", [{
702
703
  field: "room_id",
703
704
  value: roomId
704
705
  }, {
@@ -713,7 +714,7 @@ var RoomService = class {
713
714
  state_key: sk,
714
715
  event_id: newIds[k]
715
716
  });
716
- if (type === "m.room.member") await db.upsert("memberships", {
717
+ if (type === M.ROOM.MEMBER) await db.upsert("memberships", {
717
718
  room_id: roomId,
718
719
  user_id: sk,
719
720
  membership: pdus.get(newIds[k])?.content?.membership ?? "leave",
@@ -732,7 +733,7 @@ var RoomService = class {
732
733
  });
733
734
  for (const k of Object.keys(oldIds)) {
734
735
  const [type, userId] = splitStateKey(k);
735
- if (type !== "m.room.member" || this.hs.isLocalUser(userId) || oldIds[k] === newIds[k]) continue;
736
+ if (type !== M.ROOM.MEMBER || this.hs.isLocalUser(userId) || oldIds[k] === newIds[k]) continue;
736
737
  if (!await this.hs.keys.isTracked(userId)) await this.hs.keys.dropRemoteCache(userId);
737
738
  }
738
739
  for (const s of this.hs.federation.serversInRoom(await this.currentState(roomId), false)) joinedServers.add(s);
@@ -784,7 +785,7 @@ var RoomService = class {
784
785
  unsigned.prev_sender = prevState.sender;
785
786
  unsigned.replaces_state = prevState.event_id;
786
787
  }
787
- if (pdu.type === "m.room.member" && ["invite", "knock"].includes(pdu.content.membership) && opts.local) unsigned[pdu.content.membership === "invite" ? "invite_room_state" : "knock_room_state"] = this.strippedState(beforeMap);
788
+ if (pdu.type === M.ROOM.MEMBER && ["invite", "knock"].includes(pdu.content.membership) && opts.local) unsigned[pdu.content.membership === "invite" ? "invite_room_state" : "knock_room_state"] = this.strippedState(beforeMap);
788
789
  const stored = Object.keys(unsigned).length ? {
789
790
  ...pdu,
790
791
  unsigned
@@ -794,9 +795,9 @@ var RoomService = class {
794
795
  room_id: roomId
795
796
  };
796
797
  const txnToken = opts.txnId && opts.deviceId ? txnTokenOf(sender, opts.deviceId) : null;
797
- const relation = pdu.content?.["m.relates_to"];
798
+ const relation = pdu.content?.[M.RELATES_TO];
798
799
  const redactsId = features.v11 ? pdu.content?.redacts : pdu.redacts;
799
- const redaction = pdu.type === "m.room.redaction" && typeof redactsId === "string" ? await this.prepareRedaction(redactsId, view, version) : null;
800
+ const redaction = pdu.type === M.ROOM.REDACTION && typeof redactsId === "string" ? await this.prepareRedaction(redactsId, view, version) : null;
800
801
  let newExtremities = null;
801
802
  let stateChanges = [];
802
803
  let newCurrentIds = null;
@@ -812,7 +813,7 @@ var RoomService = class {
812
813
  const snap = await this.snapshotOf(ext);
813
814
  if (snap !== null) sets.push(await this.snapshotIds(snap));
814
815
  }
815
- const createEvent = features.v12 ? beforeMap.get(stateKey("m.room.create", "")) ?? (pdu.type === "m.room.create" ? pdu : void 0) : void 0;
816
+ const createEvent = features.v12 ? beforeMap.get(stateKey(M.ROOM.CREATE, "")) ?? (pdu.type === M.ROOM.CREATE ? pdu : void 0) : void 0;
816
817
  newCurrentIds = await resolveState(sets, version, async (ids) => {
817
818
  const found = await this.getPdus(ids);
818
819
  if (ids.includes(eventId)) found.set(eventId, view);
@@ -847,7 +848,7 @@ var RoomService = class {
847
848
  field: "event_id",
848
849
  value: eventId
849
850
  }]);
850
- const edge = pdu.content?.["m.relationship"];
851
+ const edge = pdu.content?.[M.RELATIONSHIP];
851
852
  if (isObject(edge) && typeof edge.event_id === "string" && typeof edge.rel_type === "string") await db.upsert("event_edges", {
852
853
  event_id: eventId,
853
854
  room_id: roomId,
@@ -896,7 +897,7 @@ var RoomService = class {
896
897
  ];
897
898
  if (change.eventId === null) {
898
899
  await db.delete("current_state", where);
899
- if (type === "m.room.member") await db.delete("memberships", [{
900
+ if (type === M.ROOM.MEMBER) await db.delete("memberships", [{
900
901
  field: "room_id",
901
902
  value: roomId
902
903
  }, {
@@ -911,7 +912,7 @@ var RoomService = class {
911
912
  state_key: sk,
912
913
  event_id: change.eventId
913
914
  });
914
- if (type === "m.room.member") {
915
+ if (type === M.ROOM.MEMBER) {
915
916
  const member = changedPdus.get(change.eventId);
916
917
  await db.upsert("memberships", {
917
918
  room_id: roomId,
@@ -1052,6 +1053,31 @@ var RoomService = class {
1052
1053
  value: membership
1053
1054
  }] })).map((r) => r.room_id);
1054
1055
  }
1056
+ /**
1057
+ * Membership changes after `since` in the rooms where `userId` has a membership (in any state),
1058
+ * given the user's own membership rows if the caller has them.
1059
+ *
1060
+ * https://github.com/k18k/matrix-nodejs-homeserver/issues/31: /sync and device-list tracking
1061
+ * read every membership change on the server since their token and kept those in the user's
1062
+ * rooms; while many users joined, each sync parsed thousands of rows. This reads those rooms only.
1063
+ */
1064
+ async membershipChangesSince(userId, since, mine) {
1065
+ const own = mine ?? await this.hs.db.findMany("memberships", { where: [{
1066
+ field: "user_id",
1067
+ value: userId
1068
+ }] });
1069
+ const rooms = [...new Set(own.map((m) => m.room_id))];
1070
+ if (rooms.length === 0) return [];
1071
+ return this.hs.db.findMany("memberships", { where: [{
1072
+ field: "room_id",
1073
+ operator: "in",
1074
+ value: rooms
1075
+ }, {
1076
+ field: "stream",
1077
+ operator: "gt",
1078
+ value: since
1079
+ }] });
1080
+ }
1055
1081
  /** All users sharing at least one joined room with `userId` (including themselves). */
1056
1082
  async usersSharingRooms(userId) {
1057
1083
  const cached = this.sharingCache.get(userId);
@@ -1106,15 +1132,15 @@ var RoomService = class {
1106
1132
  */
1107
1133
  strippedState(state) {
1108
1134
  const types = [
1109
- "m.room.create",
1110
- "m.room.join_rules",
1111
- "m.room.name",
1112
- "m.room.avatar",
1113
- "m.room.canonical_alias",
1114
- "m.room.encryption",
1115
- "m.room.topic"
1135
+ M.ROOM.CREATE,
1136
+ M.ROOM.JOIN_RULES,
1137
+ M.ROOM.NAME,
1138
+ M.ROOM.AVATAR,
1139
+ M.ROOM.CANONICAL_ALIAS,
1140
+ M.ROOM.ENCRYPTION,
1141
+ M.ROOM.TOPIC
1116
1142
  ];
1117
- const create = getState(state, "m.room.create");
1143
+ const create = getState(state, M.ROOM.CREATE);
1118
1144
  const version = typeof create?.content?.room_version === "string" ? create.content.room_version : "1";
1119
1145
  const f = roomVersionFeatures(version);
1120
1146
  const out = [];
@@ -1124,7 +1150,7 @@ var RoomService = class {
1124
1150
  const { unsigned, ...full } = ev;
1125
1151
  const wire = { ...full };
1126
1152
  if (!f.legacyEventIds) delete wire.event_id;
1127
- if (f.v12 && t === "m.room.create") delete wire.room_id;
1153
+ if (f.v12 && t === M.ROOM.CREATE) delete wire.room_id;
1128
1154
  out.push(wire);
1129
1155
  }
1130
1156
  return out;
@@ -1143,7 +1169,7 @@ var RoomService = class {
1143
1169
  },
1144
1170
  {
1145
1171
  field: "type",
1146
- value: "m.room.member"
1172
+ value: M.ROOM.MEMBER
1147
1173
  },
1148
1174
  {
1149
1175
  field: "state_key",
@@ -1160,18 +1186,18 @@ var RoomService = class {
1160
1186
  const ids = await this.snapshotIds(raw.snapshot);
1161
1187
  let membership = "leave";
1162
1188
  if (userId) {
1163
- const memId = ids[stateKey("m.room.member", userId)];
1189
+ const memId = ids[stateKey(M.ROOM.MEMBER, userId)];
1164
1190
  membership = memId ? (await this.getEvent(memId))?.pdu.content?.membership ?? "leave" : "leave";
1165
1191
  }
1166
1192
  const ev = userId ? {
1167
1193
  ...raw,
1168
1194
  viewerMembership: membership
1169
1195
  } : raw;
1170
- if (userId && ev.pdu.type === "m.room.member" && ev.pdu.state_key === userId) {
1196
+ if (userId && ev.pdu.type === M.ROOM.MEMBER && ev.pdu.state_key === userId) {
1171
1197
  out.push(ev);
1172
1198
  continue;
1173
1199
  }
1174
- const hvId = ids[stateKey("m.room.history_visibility", "")];
1200
+ const hvId = ids[stateKey(M.ROOM.HISTORY_VISIBILITY, "")];
1175
1201
  const hv = hvId ? (await this.getEvent(hvId))?.pdu.content?.history_visibility ?? "shared" : "shared";
1176
1202
  if (hv === "world_readable") {
1177
1203
  out.push(ev);
@@ -1200,7 +1226,7 @@ var RoomService = class {
1200
1226
  async canPeek(roomId, userId) {
1201
1227
  const state = await this.currentState(roomId);
1202
1228
  const membership = membershipOf(state, userId);
1203
- const hv = getState(state, "m.room.history_visibility")?.content?.history_visibility ?? "shared";
1229
+ const hv = getState(state, M.ROOM.HISTORY_VISIBILITY)?.content?.history_visibility ?? "shared";
1204
1230
  return {
1205
1231
  joined: membership === "join",
1206
1232
  worldReadable: hv === "world_readable",
@@ -1210,7 +1236,7 @@ var RoomService = class {
1210
1236
  /** The state snapshot visible to a user who is no longer joined (state at their leave). */
1211
1237
  async membershipEvent(roomId, userId) {
1212
1238
  const state = await this.currentState(roomId);
1213
- const ev = getState(state, "m.room.member", userId);
1239
+ const ev = getState(state, M.ROOM.MEMBER, userId);
1214
1240
  return ev?.event_id ? this.getEvent(ev.event_id) : null;
1215
1241
  }
1216
1242
  formatEvent(ev, viewer, opts = {}) {
@@ -1278,7 +1304,7 @@ var RoomService = class {
1278
1304
  if (features.v12) {
1279
1305
  prebuiltCreate = finalisePdu({
1280
1306
  sender,
1281
- type: "m.room.create",
1307
+ type: M.ROOM.CREATE,
1282
1308
  state_key: "",
1283
1309
  content: createContent,
1284
1310
  origin_server_ts: Date.now(),
@@ -1301,30 +1327,31 @@ var RoomService = class {
1301
1327
  const pdu = prebuiltCreate;
1302
1328
  await this.withLock(roomId, () => this.persistEvent(roomId, version, pdu, { local: true }));
1303
1329
  } else await send({
1304
- type: "m.room.create",
1330
+ type: M.ROOM.CREATE,
1305
1331
  state_key: "",
1306
1332
  content: createContent
1307
1333
  });
1334
+ const profile = await this.hs.profile(sender);
1308
1335
  await send({
1309
- type: "m.room.member",
1336
+ type: M.ROOM.MEMBER,
1310
1337
  state_key: sender,
1311
1338
  content: {
1312
1339
  membership: "join",
1313
- ...await this.hs.profile(sender)
1340
+ ...profile
1314
1341
  }
1315
1342
  });
1316
1343
  const pl = {
1317
1344
  users: features.v12 ? {} : { [sender]: 100 },
1318
1345
  users_default: 0,
1319
1346
  events: {
1320
- "m.room.name": 50,
1321
- "m.room.power_levels": 100,
1322
- "m.room.history_visibility": 100,
1323
- "m.room.canonical_alias": 50,
1324
- "m.room.avatar": 50,
1325
- "m.room.tombstone": features.v12 ? 150 : 100,
1326
- "m.room.server_acl": 100,
1327
- "m.room.encryption": 100
1347
+ [M.ROOM.NAME]: 50,
1348
+ [M.ROOM.POWER_LEVELS]: 100,
1349
+ [M.ROOM.HISTORY_VISIBILITY]: 100,
1350
+ [M.ROOM.CANONICAL_ALIAS]: 50,
1351
+ [M.ROOM.AVATAR]: 50,
1352
+ [M.ROOM.TOMBSTONE]: features.v12 ? 150 : 100,
1353
+ [M.ROOM.SERVER_ACL]: 100,
1354
+ [M.ROOM.ENCRYPTION]: 100
1328
1355
  },
1329
1356
  events_default: 0,
1330
1357
  state_default: 50,
@@ -1338,7 +1365,7 @@ var RoomService = class {
1338
1365
  const override = body.power_level_content_override;
1339
1366
  if (override !== void 0 && !isObject(override)) throw errors.badJson("power_level_content_override must be an object");
1340
1367
  const initialState = body.initial_state ?? [];
1341
- const initialPl = initialState.find((e) => e?.type === "m.room.power_levels" && (e.state_key ?? "") === "");
1368
+ const initialPl = initialState.find((e) => e?.type === M.ROOM.POWER_LEVELS && (e.state_key ?? "") === "");
1342
1369
  const predecessor = createContent.predecessor?.room_id;
1343
1370
  if (typeof predecessor === "string") await copyRoomPushRules(this.hs, sender, predecessor, roomId);
1344
1371
  const plContent = {
@@ -1348,7 +1375,7 @@ var RoomService = class {
1348
1375
  };
1349
1376
  validatePowerLevelContent(plContent, creators);
1350
1377
  await send({
1351
- type: "m.room.power_levels",
1378
+ type: M.ROOM.POWER_LEVELS,
1352
1379
  state_key: "",
1353
1380
  content: plContent
1354
1381
  });
@@ -1359,13 +1386,13 @@ var RoomService = class {
1359
1386
  creator: sender
1360
1387
  });
1361
1388
  await send({
1362
- type: "m.room.canonical_alias",
1389
+ type: M.ROOM.CANONICAL_ALIAS,
1363
1390
  state_key: "",
1364
1391
  content: { alias }
1365
1392
  });
1366
1393
  }
1367
- const presetState = [["m.room.join_rules", { join_rule: preset === "public_chat" ? "public" : "invite" }], ["m.room.history_visibility", { history_visibility: "shared" }]];
1368
- if (preset !== "public_chat") presetState.push(["m.room.guest_access", { guest_access: "can_join" }]);
1394
+ const presetState = [[M.ROOM.JOIN_RULES, { join_rule: preset === "public_chat" ? "public" : "invite" }], [M.ROOM.HISTORY_VISIBILITY, { history_visibility: "shared" }]];
1395
+ if (preset !== "public_chat") presetState.push([M.ROOM.GUEST_ACCESS, { guest_access: "can_join" }]);
1369
1396
  const initialKeys = new Set(initialState.map((e) => stateKey(e?.type, e?.state_key ?? "")));
1370
1397
  for (const [type, content] of presetState) if (!initialKeys.has(stateKey(type, ""))) await send({
1371
1398
  type,
@@ -1374,7 +1401,7 @@ var RoomService = class {
1374
1401
  });
1375
1402
  for (const ev of initialState) {
1376
1403
  if (!isObject(ev) || typeof ev.type !== "string" || !isObject(ev.content)) throw errors.badJson("Invalid initial_state event");
1377
- if (ev.type === "m.room.power_levels" || ev.type === "m.room.create" || ev.type === "m.room.member") continue;
1404
+ if (ev.type === M.ROOM.POWER_LEVELS || ev.type === M.ROOM.CREATE || ev.type === M.ROOM.MEMBER) continue;
1378
1405
  await send({
1379
1406
  type: ev.type,
1380
1407
  state_key: ev.state_key ?? "",
@@ -1382,16 +1409,16 @@ var RoomService = class {
1382
1409
  });
1383
1410
  }
1384
1411
  if (body.name !== void 0) await send({
1385
- type: "m.room.name",
1412
+ type: M.ROOM.NAME,
1386
1413
  state_key: "",
1387
1414
  content: { name: body.name }
1388
1415
  });
1389
1416
  if (body.topic !== void 0) await send({
1390
- type: "m.room.topic",
1417
+ type: M.ROOM.TOPIC,
1391
1418
  state_key: "",
1392
1419
  content: {
1393
1420
  topic: body.topic,
1394
- "m.topic": { "m.text": [{
1421
+ [M.TOPIC]: { [M.TEXT]: [{
1395
1422
  mimetype: "text/plain",
1396
1423
  body: body.topic
1397
1424
  }] }
@@ -1451,7 +1478,7 @@ var RoomService = class {
1451
1478
  if (!await this.exists(roomId)) return false;
1452
1479
  for (const [key, ev] of await this.currentState(roomId)) {
1453
1480
  const [type, userId] = splitStateKey(key);
1454
- if (type === "m.room.member" && ev.content?.membership === "join" && this.hs.isLocalUser(userId)) return true;
1481
+ if (type === M.ROOM.MEMBER && ev.content?.membership === "join" && this.hs.isLocalUser(userId)) return true;
1455
1482
  }
1456
1483
  return false;
1457
1484
  }
@@ -1536,9 +1563,9 @@ var RoomService = class {
1536
1563
  ...await this.hs.profile(userId)
1537
1564
  };
1538
1565
  if (extra.reason) content.reason = extra.reason;
1539
- const existing = getState(state, "m.room.member", userId);
1566
+ const existing = getState(state, M.ROOM.MEMBER, userId);
1540
1567
  if (existing?.content?.membership === "join" && JSON.stringify(existing.content) === JSON.stringify(content)) return roomId;
1541
- const joinRule = getState(state, "m.room.join_rules")?.content?.join_rule;
1568
+ const joinRule = getState(state, M.ROOM.JOIN_RULES)?.content?.join_rule;
1542
1569
  const current = membershipOf(state, userId);
1543
1570
  if ((joinRule === "restricted" || joinRule === "knock_restricted") && current !== "join" && current !== "invite") {
1544
1571
  const via = await this.findRestrictedAuthoriser(userId, state, version);
@@ -1548,7 +1575,7 @@ var RoomService = class {
1548
1575
  const servers = /* @__PURE__ */ new Set();
1549
1576
  for (const [k, ev] of state) {
1550
1577
  const [type, member] = splitStateKey(k);
1551
- if (type === "m.room.member" && ev.content?.membership === "join" && !this.hs.isLocalUser(member) && userLevel(pl, member) >= pl.invite) servers.add(member.slice(member.indexOf(":") + 1));
1578
+ if (type === M.ROOM.MEMBER && ev.content?.membership === "join" && !this.hs.isLocalUser(member) && userLevel(pl, member) >= pl.invite) servers.add(member.slice(member.indexOf(":") + 1));
1552
1579
  }
1553
1580
  if (servers.size) {
1554
1581
  await this.hs.federation.joinRemoteRoom(userId, roomId, [...servers], content);
@@ -1557,30 +1584,30 @@ var RoomService = class {
1557
1584
  }
1558
1585
  }
1559
1586
  await this.sendEvent(roomId, userId, {
1560
- type: "m.room.member",
1587
+ type: M.ROOM.MEMBER,
1561
1588
  state_key: userId,
1562
1589
  content
1563
1590
  });
1564
- const predecessor = getState(state, "m.room.create")?.content?.predecessor?.room_id;
1591
+ const predecessor = getState(state, M.ROOM.CREATE)?.content?.predecessor?.room_id;
1565
1592
  if (typeof predecessor === "string") await copyRoomPushRules(this.hs, userId, predecessor, roomId);
1566
1593
  return roomId;
1567
1594
  }
1568
1595
  /** Whether an allowed room exists that we can't check (or the user is joined to), so another server might authorise. */
1569
1596
  async mayQualifyRemotely(userId, state) {
1570
- const allow = getState(state, "m.room.join_rules")?.content?.allow;
1597
+ const allow = getState(state, M.ROOM.JOIN_RULES)?.content?.allow;
1571
1598
  if (!Array.isArray(allow)) return false;
1572
1599
  for (const rule of allow) {
1573
- if (rule?.type !== "m.room_membership" || typeof rule.room_id !== "string") continue;
1600
+ if (rule?.type !== M.ROOM_MEMBERSHIP || typeof rule.room_id !== "string") continue;
1574
1601
  if (!await this.serverIsJoined(rule.room_id)) return true;
1575
1602
  if (await this.membership(rule.room_id, userId) === "join") return true;
1576
1603
  }
1577
1604
  return false;
1578
1605
  }
1579
1606
  async findRestrictedAuthoriser(userId, state, version) {
1580
- const allow = getState(state, "m.room.join_rules")?.content?.allow;
1607
+ const allow = getState(state, M.ROOM.JOIN_RULES)?.content?.allow;
1581
1608
  if (!Array.isArray(allow)) return null;
1582
1609
  let permitted = false;
1583
- for (const rule of allow) if (rule?.type === "m.room_membership" && typeof rule.room_id === "string" && await this.exists(rule.room_id)) {
1610
+ for (const rule of allow) if (rule?.type === M.ROOM_MEMBERSHIP && typeof rule.room_id === "string" && await this.exists(rule.room_id)) {
1584
1611
  if (await this.membership(rule.room_id, userId) === "join") {
1585
1612
  permitted = true;
1586
1613
  break;
@@ -1590,7 +1617,7 @@ var RoomService = class {
1590
1617
  const pl = powerLevels(state, version);
1591
1618
  for (const [k, ev] of state) {
1592
1619
  const [type, key] = splitStateKey(k);
1593
- if (type === "m.room.member" && ev.content?.membership === "join" && this.hs.isLocalUser(key) && userLevel(pl, key) >= pl.invite) return key;
1620
+ if (type === M.ROOM.MEMBER && ev.content?.membership === "join" && this.hs.isLocalUser(key) && userLevel(pl, key) >= pl.invite) return key;
1594
1621
  }
1595
1622
  return null;
1596
1623
  }
@@ -1601,7 +1628,7 @@ var RoomService = class {
1601
1628
  field: "user_id",
1602
1629
  value: target
1603
1630
  }]) && !await this.hs.appservices.queryUser(target)) throw errors.notFound("Unknown user");
1604
- if (local && await inviteDecision(this.hs, target, sender) === "block") throw new MatrixError(403, "M_FORBIDDEN", "This user does not accept invites from you", { "org.matrix.msc4155.errcode": "M_INVITE_BLOCKED" });
1631
+ if (local && await inviteDecision(this.hs, target, sender) === "block") throw new MatrixError(403, "M_FORBIDDEN", "This user does not accept invites from you", { [UNSTABLE.ORG.MATRIX.MSC4155.ERRCODE]: "M_INVITE_BLOCKED" });
1605
1632
  await this.requireJoined(roomId, sender);
1606
1633
  const state = await this.currentState(roomId);
1607
1634
  if (membershipOf(state, target) === "invite") return;
@@ -1612,7 +1639,7 @@ var RoomService = class {
1612
1639
  if (opts.thirdPartyInvite) content.third_party_invite = opts.thirdPartyInvite;
1613
1640
  if (local) {
1614
1641
  await this.sendEvent(roomId, sender, {
1615
- type: "m.room.member",
1642
+ type: M.ROOM.MEMBER,
1616
1643
  state_key: target,
1617
1644
  content
1618
1645
  });
@@ -1622,12 +1649,12 @@ var RoomService = class {
1622
1649
  await this.withLock(roomId, async () => {
1623
1650
  const version = await this.roomVersion(roomId);
1624
1651
  const pdu = await this.buildLocalEvent(roomId, version, sender, {
1625
- type: "m.room.member",
1652
+ type: M.ROOM.MEMBER,
1626
1653
  state_key: target,
1627
1654
  content
1628
1655
  });
1629
1656
  const inviteState = [...this.strippedState(await this.currentState(roomId))];
1630
- const senderMember = getState(await this.currentState(roomId), "m.room.member", sender);
1657
+ const senderMember = getState(await this.currentState(roomId), M.ROOM.MEMBER, sender);
1631
1658
  if (senderMember) {
1632
1659
  const { unsigned, ...full } = senderMember;
1633
1660
  inviteState.push(this.hs.federation.toFederationPdu(full, version));
@@ -1646,11 +1673,11 @@ var RoomService = class {
1646
1673
  const content = { membership };
1647
1674
  if (reason) content.reason = reason;
1648
1675
  if (membership === "ban" || membership === "leave" && sender !== target) {
1649
- const prev = getState(await this.currentState(roomId), "m.room.member", target);
1676
+ const prev = getState(await this.currentState(roomId), M.ROOM.MEMBER, target);
1650
1677
  if (prev?.content?.displayname) content.displayname = prev.content.displayname;
1651
1678
  }
1652
1679
  await this.sendEvent(roomId, sender, {
1653
- type: "m.room.member",
1680
+ type: M.ROOM.MEMBER,
1654
1681
  state_key: target,
1655
1682
  content
1656
1683
  });
@@ -1689,7 +1716,7 @@ var RoomService = class {
1689
1716
  leave = finalisePdu({
1690
1717
  room_id: roomId,
1691
1718
  sender: userId,
1692
- type: "m.room.member",
1719
+ type: M.ROOM.MEMBER,
1693
1720
  state_key: userId,
1694
1721
  content,
1695
1722
  origin_server_ts: Date.now(),
@@ -1704,7 +1731,7 @@ var RoomService = class {
1704
1731
  async propagateProfile(userId) {
1705
1732
  const profile = await this.hs.profile(userId);
1706
1733
  for (const roomId of await this.roomsForUser(userId)) {
1707
- const prev = getState(await this.currentState(roomId), "m.room.member", userId);
1734
+ const prev = getState(await this.currentState(roomId), M.ROOM.MEMBER, userId);
1708
1735
  if (!prev) continue;
1709
1736
  const content = {
1710
1737
  ...prev.content,
@@ -1715,7 +1742,7 @@ var RoomService = class {
1715
1742
  if (profile.avatar_url === void 0) delete content.avatar_url;
1716
1743
  try {
1717
1744
  await this.sendEvent(roomId, userId, {
1718
- type: "m.room.member",
1745
+ type: M.ROOM.MEMBER,
1719
1746
  state_key: userId,
1720
1747
  content
1721
1748
  });
@@ -0,0 +1,23 @@
1
+ //#region src/core/semaphore.ts
2
+ /** Runs at most `limit` tasks at once; the others wait their turn, first come first served. */
3
+ var Semaphore = class {
4
+ limit;
5
+ active = 0;
6
+ waiting = [];
7
+ constructor(limit) {
8
+ this.limit = limit;
9
+ }
10
+ async run(fn) {
11
+ if (this.active >= this.limit) await new Promise((resolve) => this.waiting.push(resolve));
12
+ else this.active++;
13
+ try {
14
+ return await fn();
15
+ } finally {
16
+ const next = this.waiting.shift();
17
+ if (next) next();
18
+ else this.active--;
19
+ }
20
+ }
21
+ };
22
+ //#endregion
23
+ export { Semaphore };
@@ -28,11 +28,25 @@ export interface SsoProvider {
28
28
  }
29
29
  export interface SsoOptions {
30
30
  providers: SsoProvider[];
31
+ /**
32
+ * Clients that receive the login token straight away, as URL prefixes such as
33
+ * `"https://app.element.io/"` (same scheme and host, path starting with the prefix's path).
34
+ * Other clients get a page naming them, where the user confirms before the token is sent.
35
+ */
36
+ clientAllowlist?: string[];
37
+ }
38
+ /** Where the browser goes after login, and whether the user has to confirm that client first. */
39
+ export interface SsoResult {
40
+ url: string;
41
+ confirm: boolean;
42
+ /** The client as shown to the user: its host, or its scheme for app links. */
43
+ client: string;
31
44
  }
32
45
  /** Upstream OIDC login (authorization code + PKCE) issuing Matrix login tokens. */
33
46
  export declare class SsoService {
34
47
  private hs;
35
48
  readonly providers: SsoProvider[];
49
+ private clientAllowlist;
36
50
  private discovery;
37
51
  constructor(hs: Homeserver, options: SsoOptions);
38
52
  private callbackUrl;
@@ -40,7 +54,7 @@ export declare class SsoService {
40
54
  /** URL to send the user's browser to. */
41
55
  startLogin(providerId: string | null, redirectUrl: string): Promise<string>;
42
56
  /** Handle the provider's redirect; returns the client URL carrying a login token. */
43
- finishLogin(providerId: string, params: URLSearchParams): Promise<string>;
57
+ finishLogin(providerId: string, params: URLSearchParams): Promise<SsoResult>;
44
58
  private resolveUser;
45
59
  }
46
60
  //#endregion