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.
- package/README.md +89 -11
- package/dist/adapters/cluster.d.ts +1 -26
- package/dist/adapters/cluster.js +50 -103
- package/dist/api/account.js +407 -354
- package/dist/api/admin.js +82 -61
- package/dist/api/appservice.js +37 -25
- package/dist/api/backup.js +73 -62
- package/dist/api/federation.js +172 -138
- package/dist/api/filters.js +4 -3
- package/dist/api/legacy.js +91 -78
- package/dist/api/media.js +71 -55
- package/dist/api/misc.js +46 -37
- package/dist/api/preview.js +37 -73
- package/dist/api/profile.js +108 -97
- package/dist/api/push.js +114 -99
- package/dist/api/relationships.js +64 -58
- package/dist/api/rooms.js +703 -580
- package/dist/api/rtc.d.ts +0 -1
- package/dist/api/rtc.js +48 -34
- package/dist/api/search.js +133 -127
- package/dist/api/slidingsync.js +41 -31
- package/dist/api/spaces.js +82 -77
- package/dist/api/sync.js +325 -135
- package/dist/api/threadsubs.js +72 -61
- package/dist/api/threepid.js +99 -66
- package/dist/api/uia.js +17 -16
- package/dist/api/user.js +221 -178
- package/dist/core/admin.d.ts +0 -1
- package/dist/core/admin.js +6 -5
- package/dist/core/adminroom.js +9 -8
- package/dist/core/appservices.d.ts +2 -1
- package/dist/core/appservices.js +5 -4
- package/dist/core/audit.d.ts +0 -1
- package/dist/core/authrules.js +26 -25
- package/dist/core/cluster.d.ts +12 -5
- package/dist/core/cluster.js +46 -29
- package/dist/core/delayed.js +2 -1
- package/dist/core/events.js +11 -10
- package/dist/core/html.js +13 -0
- package/dist/core/identity.js +1 -1
- package/dist/core/invitefilter.js +2 -1
- package/dist/core/json.js +10 -1
- package/dist/core/keys.d.ts +10 -1
- package/dist/core/keys.js +62 -42
- package/dist/core/m.d.ts +209 -0
- package/dist/core/m.js +182 -0
- package/dist/core/outbound.d.ts +16 -0
- package/dist/core/outbound.js +172 -0
- package/dist/core/partialstate.js +2 -1
- package/dist/core/policy.js +4 -3
- package/dist/core/push.d.ts +0 -1
- package/dist/core/push.js +41 -28
- package/dist/core/pushrules.js +36 -35
- package/dist/core/ratelimit.d.ts +1 -1
- package/dist/core/rooms.d.ts +10 -1
- package/dist/core/rooms.js +101 -74
- package/dist/core/semaphore.js +23 -0
- package/dist/core/sso.d.ts +15 -1
- package/dist/core/sso.js +38 -2
- package/dist/core/stateres.js +7 -6
- package/dist/core/threepid.js +1 -9
- package/dist/federation/client.js +9 -1
- package/dist/federation/index.d.ts +2 -2
- package/dist/federation/index.js +43 -35
- package/dist/homeserver.d.ts +23 -4
- package/dist/homeserver.js +12 -3
- package/dist/http/handler.js +2 -23
- package/dist/http/input.js +2 -2
- package/dist/http/procedure.js +25 -0
- package/dist/http/router.d.ts +9 -26
- package/dist/http/router.js +10 -30
- package/dist/index.d.ts +4 -3
- package/dist/index.js +3 -2
- package/dist/storage/batch.js +11 -0
- package/dist/storage/memory.js +1 -0
- package/dist/storage/schema.js +1 -1
- package/dist/storage/sql.js +20 -1
- package/dist/storage/types.d.ts +16 -0
- package/dist/vendor/zod/LICENSE +21 -0
- package/dist/vendor/zod/v4/classic/errors.js +54 -0
- package/dist/vendor/zod/v4/classic/parse.js +17 -0
- package/dist/vendor/zod/v4/classic/schemas.js +865 -0
- package/dist/vendor/zod/v4/core/api.js +512 -0
- package/dist/vendor/zod/v4/core/checks.js +300 -0
- package/dist/vendor/zod/v4/core/core.js +109 -0
- package/dist/vendor/zod/v4/core/doc.js +35 -0
- package/dist/vendor/zod/v4/core/errors.js +118 -0
- package/dist/vendor/zod/v4/core/json-schema-processors.js +428 -0
- package/dist/vendor/zod/v4/core/memoizer.js +255 -0
- package/dist/vendor/zod/v4/core/parse.js +206 -0
- package/dist/vendor/zod/v4/core/regexes.js +73 -0
- package/dist/vendor/zod/v4/core/registries.js +48 -0
- package/dist/vendor/zod/v4/core/schemas.js +1378 -0
- package/dist/vendor/zod/v4/core/to-json-schema.js +448 -0
- package/dist/vendor/zod/v4/core/util.js +537 -0
- package/dist/vendor/zod/v4/core/versions.js +8 -0
- package/dist/vendor/zod/v4/locales/en.js +110 -0
- package/package.json +8 -6
- package/dist/core/pushrules.d.ts +0 -1
package/dist/core/rooms.js
CHANGED
|
@@ -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),
|
|
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(
|
|
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 ===
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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 ===
|
|
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 ===
|
|
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 ===
|
|
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 !==
|
|
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 ===
|
|
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?.[
|
|
798
|
+
const relation = pdu.content?.[M.RELATES_TO];
|
|
798
799
|
const redactsId = features.v11 ? pdu.content?.redacts : pdu.redacts;
|
|
799
|
-
const redaction = pdu.type ===
|
|
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(
|
|
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?.[
|
|
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 ===
|
|
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 ===
|
|
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
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
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,
|
|
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 ===
|
|
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:
|
|
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(
|
|
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 ===
|
|
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(
|
|
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,
|
|
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,
|
|
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:
|
|
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:
|
|
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:
|
|
1336
|
+
type: M.ROOM.MEMBER,
|
|
1310
1337
|
state_key: sender,
|
|
1311
1338
|
content: {
|
|
1312
1339
|
membership: "join",
|
|
1313
|
-
...
|
|
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
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
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 ===
|
|
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:
|
|
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:
|
|
1389
|
+
type: M.ROOM.CANONICAL_ALIAS,
|
|
1363
1390
|
state_key: "",
|
|
1364
1391
|
content: { alias }
|
|
1365
1392
|
});
|
|
1366
1393
|
}
|
|
1367
|
-
const presetState = [[
|
|
1368
|
-
if (preset !== "public_chat") presetState.push([
|
|
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 ===
|
|
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:
|
|
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:
|
|
1417
|
+
type: M.ROOM.TOPIC,
|
|
1391
1418
|
state_key: "",
|
|
1392
1419
|
content: {
|
|
1393
1420
|
topic: body.topic,
|
|
1394
|
-
|
|
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 ===
|
|
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,
|
|
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,
|
|
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 ===
|
|
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:
|
|
1587
|
+
type: M.ROOM.MEMBER,
|
|
1561
1588
|
state_key: userId,
|
|
1562
1589
|
content
|
|
1563
1590
|
});
|
|
1564
|
-
const predecessor = getState(state,
|
|
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,
|
|
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 !==
|
|
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,
|
|
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 ===
|
|
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 ===
|
|
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", {
|
|
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:
|
|
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:
|
|
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),
|
|
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),
|
|
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:
|
|
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:
|
|
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),
|
|
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:
|
|
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 };
|
package/dist/core/sso.d.ts
CHANGED
|
@@ -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<
|
|
57
|
+
finishLogin(providerId: string, params: URLSearchParams): Promise<SsoResult>;
|
|
44
58
|
private resolveUser;
|
|
45
59
|
}
|
|
46
60
|
//#endregion
|