matrix-nodejs-homeserver 0.2.0 → 0.3.1
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 +29 -7
- package/dist/api/account.js +385 -351
- package/dist/api/admin.js +76 -60
- 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 +70 -54
- package/dist/api/misc.js +46 -37
- package/dist/api/preview.js +4 -3
- 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 +59 -50
- 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/delayed.js +2 -1
- package/dist/core/events.js +11 -10
- package/dist/core/invitefilter.js +2 -1
- package/dist/core/keys.js +2 -1
- package/dist/core/m.d.ts +209 -0
- package/dist/core/m.js +182 -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 +12 -11
- package/dist/core/pushrules.js +36 -35
- package/dist/core/rooms.js +76 -74
- package/dist/core/stateres.js +7 -6
- package/dist/federation/client.js +2 -1
- package/dist/federation/index.d.ts +1 -1
- package/dist/federation/index.js +30 -29
- package/dist/homeserver.d.ts +2 -1
- package/dist/homeserver.js +1 -0
- 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 +2 -1
- package/dist/index.js +2 -1
- 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 +3 -5
- package/dist/core/pushrules.d.ts +0 -1
package/dist/federation/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { isObject } from "../core/json.js";
|
|
2
2
|
import { unpaddedBase64, urlSafeBase64 } from "../core/crypto.js";
|
|
3
|
+
import { M } from "../core/m.js";
|
|
3
4
|
import { SUPPORTED_ROOM_VERSIONS, computeContentHash, computeReferenceHash, eventIdOf, finalisePdu, redact, roomVersionFeatures, splitStateKey, stateKey, stripState } from "../core/events.js";
|
|
4
5
|
import { MatrixError, errors } from "../core/errors.js";
|
|
5
6
|
import { parseId } from "../core/ids.js";
|
|
@@ -14,7 +15,7 @@ import { TransactionSender } from "./sender.js";
|
|
|
14
15
|
const domainOf = (id) => id.slice(id.indexOf(":") + 1);
|
|
15
16
|
/** Server ACL check (m.room.server_acl). */
|
|
16
17
|
function serverAllowedByAcl(state, serverName) {
|
|
17
|
-
const acl = getState(state,
|
|
18
|
+
const acl = getState(state, M.ROOM.SERVER_ACL)?.content;
|
|
18
19
|
if (!isObject(acl)) return true;
|
|
19
20
|
const host = serverName.replace(/:\d+$/, "").replace(/^\[|\]$/g, "");
|
|
20
21
|
if (acl.allow_ip_literals === false && (/^\d+\.\d+\.\d+\.\d+$/.test(host) || host.includes(":"))) return false;
|
|
@@ -37,7 +38,7 @@ var FederationService = class {
|
|
|
37
38
|
constructor(hs, options = {}) {
|
|
38
39
|
this.hs = hs;
|
|
39
40
|
this.options = options;
|
|
40
|
-
this.enabled = options.enabled
|
|
41
|
+
this.enabled = options.enabled === true;
|
|
41
42
|
this.fasterJoins = options.fasterJoins !== false;
|
|
42
43
|
this.client = new FederationClient(hs, { rejectUnauthorized: options.verifyTls !== false });
|
|
43
44
|
this.keyring = new KeyRing(hs);
|
|
@@ -57,7 +58,7 @@ var FederationService = class {
|
|
|
57
58
|
const out = { ...rest };
|
|
58
59
|
const f = roomVersionFeatures(version);
|
|
59
60
|
if (!f.legacyEventIds) delete out.event_id;
|
|
60
|
-
if (f.v12 && out.type ===
|
|
61
|
+
if (f.v12 && out.type === M.ROOM.CREATE) delete out.room_id;
|
|
61
62
|
return out;
|
|
62
63
|
}
|
|
63
64
|
computeEventId(raw, version) {
|
|
@@ -73,7 +74,7 @@ var FederationService = class {
|
|
|
73
74
|
async verifyPdu(raw, version, roomId) {
|
|
74
75
|
if (!isObject(raw) || typeof raw.type !== "string" || typeof raw.sender !== "string" || !isObject(raw.content)) throw errors.badJson("Malformed PDU");
|
|
75
76
|
const f = roomVersionFeatures(version);
|
|
76
|
-
if (!f.v12 || raw.type !==
|
|
77
|
+
if (!f.v12 || raw.type !== M.ROOM.CREATE) {
|
|
77
78
|
if (raw.room_id !== roomId) throw errors.badJson("PDU room_id mismatch");
|
|
78
79
|
}
|
|
79
80
|
const eventId = this.computeEventId(raw, version);
|
|
@@ -84,7 +85,7 @@ var FederationService = class {
|
|
|
84
85
|
if (f.legacyEventIds && domainOf(eventId) !== senderDomain && !await this.keyring.verifySignedBy(redacted, domainOf(eventId))) throw errors.forbidden(`Event ${eventId} is not signed by its origin`);
|
|
85
86
|
let joinAuthorisedSignatureValid;
|
|
86
87
|
const via = raw.content?.join_authorised_via_users_server;
|
|
87
|
-
if (f.restricted && raw.type ===
|
|
88
|
+
if (f.restricted && raw.type === M.ROOM.MEMBER && raw.content?.membership === "join" && typeof via === "string" && via.includes(":")) joinAuthorisedSignatureValid = await this.keyring.verifySignedBy(redacted, domainOf(via));
|
|
88
89
|
let pdu = {
|
|
89
90
|
...raw,
|
|
90
91
|
event_id: eventId
|
|
@@ -93,7 +94,7 @@ var FederationService = class {
|
|
|
93
94
|
...redact(raw, version),
|
|
94
95
|
event_id: eventId
|
|
95
96
|
};
|
|
96
|
-
if (f.v12 && pdu.type ===
|
|
97
|
+
if (f.v12 && pdu.type === M.ROOM.CREATE) pdu.room_id = roomId;
|
|
97
98
|
return {
|
|
98
99
|
pdu,
|
|
99
100
|
joinAuthorisedSignatureValid
|
|
@@ -104,7 +105,7 @@ var FederationService = class {
|
|
|
104
105
|
const servers = /* @__PURE__ */ new Set();
|
|
105
106
|
for (const [key, ev] of state) {
|
|
106
107
|
const [type, userId] = splitStateKey(key);
|
|
107
|
-
if (type ===
|
|
108
|
+
if (type === M.ROOM.MEMBER && ev.content?.membership === "join") servers.add(domainOf(userId));
|
|
108
109
|
}
|
|
109
110
|
servers.delete(this.hs.serverName);
|
|
110
111
|
for (const s of servers) if (!this.serverAllowed(s) || applyAcl && !serverAllowedByAcl(state, s)) servers.delete(s);
|
|
@@ -123,8 +124,8 @@ var FederationService = class {
|
|
|
123
124
|
onLocalEvent(pdu, state) {
|
|
124
125
|
if (!this.enabled) return;
|
|
125
126
|
(async () => {
|
|
126
|
-
const destinations = await this.roomDestinations(pdu.room_id, state, pdu.type !==
|
|
127
|
-
if (pdu.type ===
|
|
127
|
+
const destinations = await this.roomDestinations(pdu.room_id, state, pdu.type !== M.ROOM.SERVER_ACL);
|
|
128
|
+
if (pdu.type === M.ROOM.MEMBER && typeof pdu.state_key === "string" && !this.hs.isLocalUser(pdu.state_key)) destinations.add(domainOf(pdu.state_key));
|
|
128
129
|
if (!destinations.size) return;
|
|
129
130
|
const version = await this.hs.rooms.roomVersion(pdu.room_id) ?? "10";
|
|
130
131
|
await this.sender.sendPdu(destinations, this.toFederationPdu(pdu, version));
|
|
@@ -147,17 +148,17 @@ var FederationService = class {
|
|
|
147
148
|
return out;
|
|
148
149
|
}
|
|
149
150
|
async onTyping(roomId, userId, typing) {
|
|
150
|
-
if (this.hs.isLocalUser(userId)) await this.sendEduToRooms([roomId],
|
|
151
|
+
if (this.hs.isLocalUser(userId)) await this.sendEduToRooms([roomId], M.TYPING, {
|
|
151
152
|
room_id: roomId,
|
|
152
153
|
user_id: userId,
|
|
153
154
|
typing
|
|
154
155
|
}, false);
|
|
155
156
|
}
|
|
156
157
|
async onReceipt(roomId, userId, receiptType, eventId, ts, threadId) {
|
|
157
|
-
if (!this.hs.isLocalUser(userId) || receiptType ===
|
|
158
|
+
if (!this.hs.isLocalUser(userId) || receiptType === M.READ.PRIVATE) return;
|
|
158
159
|
const data = { ts };
|
|
159
160
|
if (threadId) data.thread_id = threadId;
|
|
160
|
-
await this.sendEduToRooms([roomId],
|
|
161
|
+
await this.sendEduToRooms([roomId], M.RECEIPT, { [roomId]: { [receiptType]: { [userId]: {
|
|
161
162
|
event_ids: [eventId],
|
|
162
163
|
data
|
|
163
164
|
} } } });
|
|
@@ -166,7 +167,7 @@ var FederationService = class {
|
|
|
166
167
|
if (!this.enabled || !this.hs.isLocalUser(userId)) return;
|
|
167
168
|
const servers = await this.serversSharingRooms(userId);
|
|
168
169
|
if (!servers.size) return;
|
|
169
|
-
await this.sender.sendEdu(servers,
|
|
170
|
+
await this.sender.sendEdu(servers, M.PRESENCE, { push: [{
|
|
170
171
|
user_id: userId,
|
|
171
172
|
presence,
|
|
172
173
|
last_active_ago: 0,
|
|
@@ -217,7 +218,7 @@ var FederationService = class {
|
|
|
217
218
|
const d = devices.find((x) => x.device_id === u.deviceId);
|
|
218
219
|
if (d?.display_name) content.device_display_name = d.display_name;
|
|
219
220
|
}
|
|
220
|
-
await this.sender.sendEdu(servers,
|
|
221
|
+
await this.sender.sendEdu(servers, M.DEVICE_LIST_UPDATE, content);
|
|
221
222
|
}
|
|
222
223
|
}
|
|
223
224
|
async onSigningKeyUpdate(userId) {
|
|
@@ -230,7 +231,7 @@ var FederationService = class {
|
|
|
230
231
|
}] });
|
|
231
232
|
const content = { user_id: userId };
|
|
232
233
|
for (const r of rows) if (r.key_type !== "user_signing") content[`${r.key_type}_key`] = r.key;
|
|
233
|
-
await this.sender.sendEdu(servers,
|
|
234
|
+
await this.sender.sendEdu(servers, M.SIGNING_KEY_UPDATE, content);
|
|
234
235
|
}
|
|
235
236
|
/** Deliver to-device messages addressed to remote users. */
|
|
236
237
|
async sendToDevice(sender, type, messages, messageId) {
|
|
@@ -241,7 +242,7 @@ var FederationService = class {
|
|
|
241
242
|
if (server === this.hs.serverName || !this.serverAllowed(server)) continue;
|
|
242
243
|
(byServer.get(server) ?? byServer.set(server, {}).get(server))[userId] = devices;
|
|
243
244
|
}
|
|
244
|
-
for (const [server, msgs] of byServer) await this.sender.sendEdu([server],
|
|
245
|
+
for (const [server, msgs] of byServer) await this.sender.sendEdu([server], M.DIRECT_TO_DEVICE, {
|
|
245
246
|
sender,
|
|
246
247
|
type,
|
|
247
248
|
message_id: `${messageId}-${server}`,
|
|
@@ -301,7 +302,7 @@ var FederationService = class {
|
|
|
301
302
|
* rescinded or their knock being accepted/rejected.
|
|
302
303
|
*/
|
|
303
304
|
async handleOutOfRoomMembership(origin, roomId, raw) {
|
|
304
|
-
if (raw?.type !==
|
|
305
|
+
if (raw?.type !== M.ROOM.MEMBER || typeof raw.state_key !== "string" || !this.hs.isLocalUser(raw.state_key)) throw errors.notFound("Unknown room");
|
|
305
306
|
const row = await this.hs.db.findOne("memberships", [{
|
|
306
307
|
field: "room_id",
|
|
307
308
|
value: roomId
|
|
@@ -311,7 +312,7 @@ var FederationService = class {
|
|
|
311
312
|
}]);
|
|
312
313
|
if (!row) throw errors.notFound("Unknown room");
|
|
313
314
|
const previous = await this.hs.rooms.getPdu(row.event_id);
|
|
314
|
-
const create = (previous?.unsigned?.invite_room_state ?? previous?.unsigned?.knock_room_state ?? []).find((e) => e?.type ===
|
|
315
|
+
const create = (previous?.unsigned?.invite_room_state ?? previous?.unsigned?.knock_room_state ?? []).find((e) => e?.type === M.ROOM.CREATE);
|
|
315
316
|
const version = typeof create?.content?.room_version === "string" ? create.content.room_version : "1";
|
|
316
317
|
const { pdu } = await this.verifyPdu(raw, version, roomId);
|
|
317
318
|
if (domainOf(pdu.sender) !== origin && domainOf(pdu.sender) !== domainOf(raw.state_key)) throw errors.forbidden("Unexpected sender");
|
|
@@ -571,7 +572,7 @@ var FederationService = class {
|
|
|
571
572
|
authPdus.push(p);
|
|
572
573
|
}
|
|
573
574
|
try {
|
|
574
|
-
const create = roomVersionFeatures(version).v12 && pdu.type !==
|
|
575
|
+
const create = roomVersionFeatures(version).v12 && pdu.type !== M.ROOM.CREATE ? await this.hs.rooms.getPdu(`$${roomId.slice(1)}`) ?? void 0 : void 0;
|
|
575
576
|
checkAuth(pdu, authStateFromAuthEvents(pdu, authPdus, version, create), version);
|
|
576
577
|
return null;
|
|
577
578
|
} catch (err) {
|
|
@@ -592,7 +593,7 @@ var FederationService = class {
|
|
|
592
593
|
const content = edu?.content;
|
|
593
594
|
if (!isObject(content)) return;
|
|
594
595
|
switch (edu.edu_type) {
|
|
595
|
-
case
|
|
596
|
+
case M.TYPING: {
|
|
596
597
|
const { room_id, user_id, typing } = content;
|
|
597
598
|
if (typeof user_id !== "string" || domainOf(user_id) !== origin || typeof room_id !== "string") return;
|
|
598
599
|
if (!await this.hs.rooms.exists(room_id) || !await this.mayActInRoom(room_id, user_id, origin)) return;
|
|
@@ -600,12 +601,12 @@ var FederationService = class {
|
|
|
600
601
|
await this.hs.typing.setTyping(room_id, user_id, !!typing, typeof content.timeout === "number" ? content.timeout : 3e4);
|
|
601
602
|
return;
|
|
602
603
|
}
|
|
603
|
-
case
|
|
604
|
+
case M.RECEIPT:
|
|
604
605
|
for (const [roomId, byType] of Object.entries(content)) {
|
|
605
606
|
if (!isObject(byType) || !await this.hs.rooms.exists(roomId)) continue;
|
|
606
607
|
if (!serverAllowedByAcl(await this.hs.rooms.currentState(roomId), origin)) continue;
|
|
607
608
|
for (const [receiptType, byUser] of Object.entries(byType)) {
|
|
608
|
-
if (
|
|
609
|
+
if (receiptType !== M.READ._ && receiptType !== M.READ.PRIVATE || !isObject(byUser)) continue;
|
|
609
610
|
for (const [userId, r] of Object.entries(byUser)) {
|
|
610
611
|
if (domainOf(userId) !== origin || !isObject(r) || !Array.isArray(r.event_ids)) continue;
|
|
611
612
|
if (!await this.mayActInRoom(roomId, userId, origin)) continue;
|
|
@@ -625,7 +626,7 @@ var FederationService = class {
|
|
|
625
626
|
this.hs.notifier.notify(await this.hs.rooms.joinedMembers(roomId));
|
|
626
627
|
}
|
|
627
628
|
return;
|
|
628
|
-
case
|
|
629
|
+
case M.PRESENCE:
|
|
629
630
|
for (const p of Array.isArray(content.push) ? content.push : []) {
|
|
630
631
|
if (!isObject(p) || typeof p.user_id !== "string" || domainOf(p.user_id) !== origin) continue;
|
|
631
632
|
if (![
|
|
@@ -643,7 +644,7 @@ var FederationService = class {
|
|
|
643
644
|
this.hs.notifier.notify(await this.hs.rooms.usersSharingRooms(p.user_id));
|
|
644
645
|
}
|
|
645
646
|
return;
|
|
646
|
-
case
|
|
647
|
+
case M.DEVICE_LIST_UPDATE: {
|
|
647
648
|
const userId = content.user_id;
|
|
648
649
|
if (typeof userId !== "string" || domainOf(userId) !== origin || typeof content.device_id !== "string") return;
|
|
649
650
|
const known = await this.hs.db.findOne("remote_device_lists", [{
|
|
@@ -655,7 +656,7 @@ var FederationService = class {
|
|
|
655
656
|
if (known || content.deleted || isObject(content.keys)) await this.hs.keys.markDeviceListChanged(userId);
|
|
656
657
|
return;
|
|
657
658
|
}
|
|
658
|
-
case
|
|
659
|
+
case M.SIGNING_KEY_UPDATE: {
|
|
659
660
|
const userId = content.user_id;
|
|
660
661
|
if (typeof userId !== "string" || domainOf(userId) !== origin) return;
|
|
661
662
|
for (const type of ["master", "self_signing"]) {
|
|
@@ -671,7 +672,7 @@ var FederationService = class {
|
|
|
671
672
|
await this.hs.keys.markDeviceListChanged(userId);
|
|
672
673
|
return;
|
|
673
674
|
}
|
|
674
|
-
case
|
|
675
|
+
case M.DIRECT_TO_DEVICE: {
|
|
675
676
|
const { sender, type, message_id, messages } = content;
|
|
676
677
|
if (typeof sender !== "string" || domainOf(sender) !== origin || typeof type !== "string" || !isObject(messages)) return;
|
|
677
678
|
if (!await this.hs.cluster.setIfAbsent(`fedtodevice:${origin}|${message_id}`, true, 864e5)) return;
|
|
@@ -751,7 +752,7 @@ var FederationService = class {
|
|
|
751
752
|
const pdu = await this.hs.rooms.getPdu(id);
|
|
752
753
|
if (!pdu) continue;
|
|
753
754
|
stateIds[stateKey(pdu.type, pdu.state_key)] = id;
|
|
754
|
-
if (pdu.type ===
|
|
755
|
+
if (pdu.type === M.ROOM.CREATE) create = pdu;
|
|
755
756
|
}
|
|
756
757
|
if (!create) throw errors.badJson("send_join response lacks the create event");
|
|
757
758
|
if (!await this.hs.rooms.exists(roomId)) await this.hs.db.upsert("rooms", {
|
|
@@ -993,14 +994,14 @@ var FederationService = class {
|
|
|
993
994
|
const out = /* @__PURE__ */ new Set();
|
|
994
995
|
for (const [key, ev] of state) {
|
|
995
996
|
const [type, userId] = splitStateKey(key);
|
|
996
|
-
if (type ===
|
|
997
|
+
if (type === M.ROOM.MEMBER && ev.content?.membership === "join") out.add(domainOf(userId));
|
|
997
998
|
}
|
|
998
999
|
return [...out];
|
|
999
1000
|
}
|
|
1000
1001
|
isMembershipVisibleTo(state, server) {
|
|
1001
1002
|
for (const [key] of state) {
|
|
1002
1003
|
const [type, userId] = splitStateKey(key);
|
|
1003
|
-
if (type ===
|
|
1004
|
+
if (type === M.ROOM.MEMBER && domainOf(userId) === server && membershipOf(state, userId) === "join") return true;
|
|
1004
1005
|
}
|
|
1005
1006
|
return false;
|
|
1006
1007
|
}
|
package/dist/homeserver.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { SigningKey } from "./core/crypto.js";
|
|
|
3
3
|
import { PolicyServers } from "./core/policy.js";
|
|
4
4
|
import { ThreepidOptions, ThreepidService } from "./core/threepid.js";
|
|
5
5
|
import { IdentityServers } from "./core/identity.js";
|
|
6
|
+
import { M } from "./core/m.js";
|
|
6
7
|
import { AppServiceRegistration, AppServiceService } from "./core/appservices.js";
|
|
7
8
|
import { AuthService } from "./core/auth.js";
|
|
8
9
|
import { KeyService } from "./core/keys.js";
|
|
@@ -52,7 +53,7 @@ export interface HomeserverOptions {
|
|
|
52
53
|
/** Contacts and page served at `/.well-known/matrix/support` (e.g. to appeal a locked account). */
|
|
53
54
|
support?: {
|
|
54
55
|
contacts?: {
|
|
55
|
-
role:
|
|
56
|
+
role: typeof M.ROLE.ADMIN | typeof M.ROLE.SECURITY | (string & {});
|
|
56
57
|
matrix_id?: string;
|
|
57
58
|
email_address?: string;
|
|
58
59
|
}[];
|
package/dist/homeserver.js
CHANGED
package/dist/http/handler.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { canonicalJson } from "../core/json.js";
|
|
2
|
+
import { UNSTABLE } from "../core/m.js";
|
|
2
3
|
import { MatrixError, errors } from "../core/errors.js";
|
|
3
|
-
import { inputError } from "./input.js";
|
|
4
4
|
//#region src/http/handler.ts
|
|
5
5
|
const CORS_HEADERS = {
|
|
6
6
|
"access-control-allow-origin": "*",
|
|
@@ -95,7 +95,7 @@ async function dispatch(hs, request, url, options) {
|
|
|
95
95
|
const reg = hs.appservices.byToken(t);
|
|
96
96
|
if (reg) {
|
|
97
97
|
const userId = await hs.appservices.masquerade(reg, url.searchParams.get("user_id"));
|
|
98
|
-
const deviceId = url.searchParams.get("device_id") ?? url.searchParams.get(
|
|
98
|
+
const deviceId = url.searchParams.get("device_id") ?? url.searchParams.get(UNSTABLE.ORG.MATRIX.MSC3202.DEVICE_ID);
|
|
99
99
|
if (deviceId && !await hs.db.findOne("devices", [{
|
|
100
100
|
field: "user_id",
|
|
101
101
|
value: userId
|
|
@@ -124,33 +124,12 @@ async function dispatch(hs, request, url, options) {
|
|
|
124
124
|
async optionalAuth() {
|
|
125
125
|
return token() ? ctx.auth() : null;
|
|
126
126
|
},
|
|
127
|
-
input: void 0,
|
|
128
127
|
async rateLimit(action) {
|
|
129
128
|
const s = token() ? await ctx.auth().catch(() => null) : null;
|
|
130
129
|
if (s?.isAdmin || s?.appservice && s.appservice.rate_limited === false) return;
|
|
131
130
|
await hs.rateLimits.check(action, s?.userId ?? ctx.ip ?? "unknown");
|
|
132
131
|
}
|
|
133
132
|
};
|
|
134
|
-
if (matched.input) {
|
|
135
|
-
const schema = matched.input;
|
|
136
|
-
let raw;
|
|
137
|
-
let readError;
|
|
138
|
-
try {
|
|
139
|
-
raw = await ctx.json();
|
|
140
|
-
} catch (err) {
|
|
141
|
-
readError = err;
|
|
142
|
-
}
|
|
143
|
-
let parsed = null;
|
|
144
|
-
Object.defineProperty(ctx, "input", { get() {
|
|
145
|
-
if (readError) throw readError;
|
|
146
|
-
if (!parsed) {
|
|
147
|
-
const result = schema.safeParse(raw);
|
|
148
|
-
if (!result.success) throw inputError(result.error);
|
|
149
|
-
parsed = { data: result.data };
|
|
150
|
-
}
|
|
151
|
-
return parsed.data;
|
|
152
|
-
} });
|
|
153
|
-
}
|
|
154
133
|
const result = await matched.handler(ctx);
|
|
155
134
|
if (result instanceof Response) return withCors(result);
|
|
156
135
|
return jsonResponse(result ?? {});
|
package/dist/http/input.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { MatrixError } from "../core/errors.js";
|
|
2
|
-
import {
|
|
2
|
+
import { looseObject } from "../vendor/zod/v4/classic/schemas.js";
|
|
3
3
|
//#region src/http/input.ts
|
|
4
4
|
/** A request body: a JSON object whose unknown keys are kept. */
|
|
5
|
-
const body = (shape) =>
|
|
5
|
+
const body = (shape) => looseObject(shape);
|
|
6
6
|
/**
|
|
7
7
|
* Errors carry their Matrix errcode in the message, as `M_CODE: text`: `fail` builds the zod
|
|
8
8
|
* error option for a field. Without one, a missing field is M_MISSING_PARAM and any other
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { inputError } from "./input.js";
|
|
2
|
+
//#region src/http/procedure.ts
|
|
3
|
+
function procedure(def) {
|
|
4
|
+
return async (ctx) => {
|
|
5
|
+
const session = def.auth === "user" ? await ctx.auth() : def.auth === "optional" ? await ctx.optionalAuth() : void 0;
|
|
6
|
+
const extra = def.guard ? await def.guard(ctx) : {};
|
|
7
|
+
if (def.rateLimit) await ctx.rateLimit(def.rateLimit);
|
|
8
|
+
let input;
|
|
9
|
+
if (def.input) {
|
|
10
|
+
const parsed = def.input.safeParse(await ctx.json());
|
|
11
|
+
if (!parsed.success) throw inputError(parsed.error);
|
|
12
|
+
input = parsed.data;
|
|
13
|
+
}
|
|
14
|
+
const result = await def.handler(Object.assign(ctx, extra, {
|
|
15
|
+
session,
|
|
16
|
+
input
|
|
17
|
+
}));
|
|
18
|
+
if (!def.output || result instanceof Response) return result;
|
|
19
|
+
const checked = def.output.safeParse(result);
|
|
20
|
+
if (!checked.success) throw new Error(`Response does not match the output schema: ${checked.error.message}`);
|
|
21
|
+
return checked.data;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
export { procedure };
|
package/dist/http/router.d.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { Session } from "../core/auth.js";
|
|
2
2
|
import { RateLimitAction } from "../core/ratelimit.js";
|
|
3
3
|
import { Homeserver } from "../homeserver.js";
|
|
4
|
-
import { z } from "zod";
|
|
5
4
|
//#region src/http/router.d.ts
|
|
6
5
|
export type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
|
|
7
|
-
export interface RequestContext
|
|
6
|
+
export interface RequestContext {
|
|
8
7
|
hs: Homeserver;
|
|
9
8
|
request: Request;
|
|
10
9
|
url: URL;
|
|
@@ -23,38 +22,22 @@ export interface RequestContext<I = unknown> {
|
|
|
23
22
|
accessToken(): string | null;
|
|
24
23
|
/** Apply the rate limit for `action` to the requesting user (or IP when unauthenticated). */
|
|
25
24
|
rateLimit(action: RateLimitAction): Promise<void>;
|
|
26
|
-
/**
|
|
27
|
-
* The request body parsed with the route's `input` schema (undefined for routes without one).
|
|
28
|
-
* Validated on first access, which throws the Matrix error for an invalid body: read it where
|
|
29
|
-
* the handler needs the body, after authenticating.
|
|
30
|
-
*/
|
|
31
|
-
input: I;
|
|
32
25
|
}
|
|
33
26
|
export type HandlerResult = Response | object | void;
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
export interface RouteOptions<S extends z.ZodType = z.ZodType> {
|
|
37
|
-
input?: S;
|
|
38
|
-
}
|
|
39
|
-
/** A route's handler, with the body type its `input` schema produces. */
|
|
40
|
-
type Routed<S> = S extends z.ZodType ? Handler<z.output<S>> : Handler;
|
|
27
|
+
/** What the router calls; endpoints build one with `procedure()` (see http/procedure.ts). */
|
|
28
|
+
export type Handler = (ctx: RequestContext) => Promise<HandlerResult> | HandlerResult;
|
|
41
29
|
export declare class Router {
|
|
42
30
|
private routes;
|
|
43
|
-
add
|
|
44
|
-
get(path: string, h: Handler)
|
|
45
|
-
post
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
put(path: string, h: Handler): void;
|
|
49
|
-
delete(path: string, h: Handler): void;
|
|
31
|
+
add(method: HttpMethod, path: string, handler: Handler): void;
|
|
32
|
+
get: (path: string, h: Handler) => void;
|
|
33
|
+
post: (path: string, h: Handler) => void;
|
|
34
|
+
put: (path: string, h: Handler) => void;
|
|
35
|
+
delete: (path: string, h: Handler) => void;
|
|
50
36
|
/** Register a client-server API route under both `r0` and `v3` (or the given versions). */
|
|
51
|
-
client<S extends z.ZodType>(method: HttpMethod, path: string, options: RouteOptions<S>, handler: Routed<S>, versions?: string[]): void;
|
|
52
37
|
client(method: HttpMethod, path: string, handler: Handler, versions?: string[]): void;
|
|
53
38
|
match(method: string, pathname: string): {
|
|
54
39
|
handler: Handler;
|
|
55
40
|
params: Record<string, string>;
|
|
56
|
-
input?: z.ZodType;
|
|
57
41
|
} | "method" | null;
|
|
58
42
|
}
|
|
59
|
-
//#endregion
|
|
60
|
-
export {};
|
|
43
|
+
//#endregion
|
package/dist/http/router.js
CHANGED
|
@@ -1,39 +1,20 @@
|
|
|
1
1
|
//#region src/http/router.ts
|
|
2
2
|
var Router = class {
|
|
3
3
|
routes = [];
|
|
4
|
-
add(method, path, handler
|
|
4
|
+
add(method, path, handler) {
|
|
5
5
|
this.routes.push({
|
|
6
6
|
method,
|
|
7
7
|
segments: path.split("/").filter(Boolean),
|
|
8
|
-
handler
|
|
9
|
-
input: options.input
|
|
8
|
+
handler
|
|
10
9
|
});
|
|
11
10
|
}
|
|
12
|
-
get(path, h)
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
put(path, a, b) {
|
|
20
|
-
if (typeof a === "function") this.add("PUT", path, a);
|
|
21
|
-
else this.add("PUT", path, b, a);
|
|
22
|
-
}
|
|
23
|
-
delete(path, h) {
|
|
24
|
-
this.add("DELETE", path, h);
|
|
25
|
-
}
|
|
26
|
-
client(method, path, a, b, c) {
|
|
27
|
-
const [options, handler, versions] = typeof a === "function" ? [
|
|
28
|
-
{},
|
|
29
|
-
a,
|
|
30
|
-
b ?? ["r0", "v3"]
|
|
31
|
-
] : [
|
|
32
|
-
a,
|
|
33
|
-
b,
|
|
34
|
-
c ?? ["r0", "v3"]
|
|
35
|
-
];
|
|
36
|
-
for (const v of versions) this.add(method, `/_matrix/client/${v}${path}`, handler, options);
|
|
11
|
+
get = (path, h) => this.add("GET", path, h);
|
|
12
|
+
post = (path, h) => this.add("POST", path, h);
|
|
13
|
+
put = (path, h) => this.add("PUT", path, h);
|
|
14
|
+
delete = (path, h) => this.add("DELETE", path, h);
|
|
15
|
+
/** Register a client-server API route under both `r0` and `v3` (or the given versions). */
|
|
16
|
+
client(method, path, handler, versions = ["r0", "v3"]) {
|
|
17
|
+
for (const v of versions) this.add(method, `/_matrix/client/${v}${path}`, handler);
|
|
37
18
|
}
|
|
38
19
|
match(method, pathname) {
|
|
39
20
|
const parts = pathname.split("/").filter(Boolean);
|
|
@@ -47,8 +28,7 @@ var Router = class {
|
|
|
47
28
|
}
|
|
48
29
|
return {
|
|
49
30
|
handler: route.handler,
|
|
50
|
-
params
|
|
51
|
-
input: route.input
|
|
31
|
+
params
|
|
52
32
|
};
|
|
53
33
|
}
|
|
54
34
|
return methodMismatch ? "method" : null;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ThreepidEmail, ThreepidOptions, ThreepidPurpose, ThreepidSms } from "./core/threepid.js";
|
|
2
|
+
import { M, UNSTABLE } from "./core/m.js";
|
|
2
3
|
import { AppServiceRegistration, loadAppServiceRegistrations, parseAppServiceRegistration } from "./core/appservices.js";
|
|
3
4
|
import { ClusterAdapter, localCluster } from "./core/cluster.js";
|
|
4
5
|
import { DatabaseAdapter, FieldDef, FindManyOptions, ModelDef, Operator, Row, Schema, WhereClause } from "./storage/types.js";
|
|
@@ -22,4 +23,4 @@ import { MysqlPoolLike, PgPoolLike, SqlAdapterOptions, SqlDialect, SqlExecutor,
|
|
|
22
23
|
import { schema } from "./storage/schema.js";
|
|
23
24
|
import { S3MediaOptions, s3Media } from "./adapters/s3.js";
|
|
24
25
|
import { PgClusterPoolLike, RedisLike, RedisSubscriberLike, postgresCluster, redisCluster } from "./adapters/cluster.js";
|
|
25
|
-
export { type AdminRoomOptions, type AdminService, type AppServiceRegistration, type AuditAction, type AuditEntry, type AuditOptions, type ClusterAdapter, type DatabaseAdapter, type DelayedEventsOptions, type FederationOptions, type FieldDef, type FindManyOptions, Homeserver, type HomeserverOptions, type Logger, MatrixError, type MediaAdapter, type MediaMetadata, type ModelDef, type MysqlPoolLike, type NodeHandler, type OAuthDelegationOptions, type Operator, type PasswordCheckContext, type PgClusterPoolLike, type PgPoolLike, type RateLimitAction, type RateLimitRule, type RateLimiter, type RedisLike, type RedisSubscriberLike, type Row, type RtcOptions, type S3MediaOptions, type Schema, type ServerNoticesOptions, type SqlAdapterOptions, type SqlDialect, type SqlExecutor, type SqliteDatabaseLike, type SsoOptions, type SsoProvider, type ThreepidEmail, type ThreepidOptions, type ThreepidPurpose, type ThreepidSms, type ThumbnailRequest, type Thumbnailer, type TurnOptions, type UserAuthAdapter, type WhereClause, createHomeserver, filesystemMedia, getExpressHandler, getFastifyPlugin, getFetchHandler, getHonoHandler, getNodeHandler, handleRequest, isMatrixPath, loadAppServiceRegistrations, localCluster, memoryAdapter, memoryMedia, memoryRateLimiter, mysqlAdapter, parseAppServiceRegistration, postgresAdapter, postgresCluster, redisCluster, s3Media, schema, sharpThumbnailer, sqlAdapter, sqliteAdapter };
|
|
26
|
+
export { type AdminRoomOptions, type AdminService, type AppServiceRegistration, type AuditAction, type AuditEntry, type AuditOptions, type ClusterAdapter, type DatabaseAdapter, type DelayedEventsOptions, type FederationOptions, type FieldDef, type FindManyOptions, Homeserver, type HomeserverOptions, type Logger, M, MatrixError, type MediaAdapter, type MediaMetadata, type ModelDef, type MysqlPoolLike, type NodeHandler, type OAuthDelegationOptions, type Operator, type PasswordCheckContext, type PgClusterPoolLike, type PgPoolLike, type RateLimitAction, type RateLimitRule, type RateLimiter, type RedisLike, type RedisSubscriberLike, type Row, type RtcOptions, type S3MediaOptions, type Schema, type ServerNoticesOptions, type SqlAdapterOptions, type SqlDialect, type SqlExecutor, type SqliteDatabaseLike, type SsoOptions, type SsoProvider, type ThreepidEmail, type ThreepidOptions, type ThreepidPurpose, type ThreepidSms, type ThumbnailRequest, type Thumbnailer, type TurnOptions, UNSTABLE, type UserAuthAdapter, type WhereClause, createHomeserver, filesystemMedia, getExpressHandler, getFastifyPlugin, getFetchHandler, getHonoHandler, getNodeHandler, handleRequest, isMatrixPath, loadAppServiceRegistrations, localCluster, memoryAdapter, memoryMedia, memoryRateLimiter, mysqlAdapter, parseAppServiceRegistration, postgresAdapter, postgresCluster, redisCluster, s3Media, schema, sharpThumbnailer, sqlAdapter, sqliteAdapter };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { M, UNSTABLE } from "./core/m.js";
|
|
1
2
|
import { MatrixError } from "./core/errors.js";
|
|
2
3
|
import { localCluster } from "./core/cluster.js";
|
|
3
4
|
import { loadAppServiceRegistrations, parseAppServiceRegistration } from "./core/appservices.js";
|
|
@@ -11,4 +12,4 @@ import { getExpressHandler, getFastifyPlugin, getFetchHandler, getHonoHandler, g
|
|
|
11
12
|
import { mysqlAdapter, postgresAdapter, sqlAdapter, sqliteAdapter } from "./storage/sql.js";
|
|
12
13
|
import { s3Media } from "./adapters/s3.js";
|
|
13
14
|
import { postgresCluster, redisCluster } from "./adapters/cluster.js";
|
|
14
|
-
export { Homeserver, MatrixError, createHomeserver, filesystemMedia, getExpressHandler, getFastifyPlugin, getFetchHandler, getHonoHandler, getNodeHandler, handleRequest, isMatrixPath, loadAppServiceRegistrations, localCluster, memoryAdapter, memoryMedia, memoryRateLimiter, mysqlAdapter, parseAppServiceRegistration, postgresAdapter, postgresCluster, redisCluster, s3Media, schema, sharpThumbnailer, sqlAdapter, sqliteAdapter };
|
|
15
|
+
export { Homeserver, M, MatrixError, UNSTABLE, createHomeserver, filesystemMedia, getExpressHandler, getFastifyPlugin, getFetchHandler, getHonoHandler, getNodeHandler, handleRequest, isMatrixPath, loadAppServiceRegistrations, localCluster, memoryAdapter, memoryMedia, memoryRateLimiter, mysqlAdapter, parseAppServiceRegistration, postgresAdapter, postgresCluster, redisCluster, s3Media, schema, sharpThumbnailer, sqlAdapter, sqliteAdapter };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Colin McDonnell
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { jsonStringifyReplacer } from "../core/util.js";
|
|
2
|
+
import { $constructor } from "../core/core.js";
|
|
3
|
+
import { $ZodError, flattenError, formatError } from "../core/errors.js";
|
|
4
|
+
//#region node_modules/zod/v4/classic/errors.js
|
|
5
|
+
const _installedErrorProtos = /* @__PURE__ */ new WeakSet([Object.prototype, Error.prototype]);
|
|
6
|
+
function _lazyMethod(proto, key, make) {
|
|
7
|
+
Object.defineProperty(proto, key, {
|
|
8
|
+
configurable: true,
|
|
9
|
+
enumerable: false,
|
|
10
|
+
get() {
|
|
11
|
+
const value = make(this);
|
|
12
|
+
Object.defineProperty(this, key, {
|
|
13
|
+
value,
|
|
14
|
+
configurable: true,
|
|
15
|
+
writable: true
|
|
16
|
+
});
|
|
17
|
+
return value;
|
|
18
|
+
},
|
|
19
|
+
set(value) {
|
|
20
|
+
Object.defineProperty(this, key, {
|
|
21
|
+
value,
|
|
22
|
+
configurable: true,
|
|
23
|
+
writable: true
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
const initializer = (inst, issues) => {
|
|
29
|
+
$ZodError.init(inst, issues);
|
|
30
|
+
inst.name = "ZodError";
|
|
31
|
+
const proto = Object.getPrototypeOf(inst);
|
|
32
|
+
if (_installedErrorProtos.has(proto)) return;
|
|
33
|
+
_installedErrorProtos.add(proto);
|
|
34
|
+
_lazyMethod(proto, "format", (self) => (mapper) => formatError(self, mapper));
|
|
35
|
+
_lazyMethod(proto, "flatten", (self) => (mapper) => flattenError(self, mapper));
|
|
36
|
+
_lazyMethod(proto, "addIssue", (self) => (issue) => {
|
|
37
|
+
self.issues.push(issue);
|
|
38
|
+
self.message = JSON.stringify(self.issues, jsonStringifyReplacer, 2);
|
|
39
|
+
});
|
|
40
|
+
_lazyMethod(proto, "addIssues", (self) => (issues) => {
|
|
41
|
+
self.issues.push(...issues);
|
|
42
|
+
self.message = JSON.stringify(self.issues, jsonStringifyReplacer, 2);
|
|
43
|
+
});
|
|
44
|
+
Object.defineProperty(proto, "isEmpty", {
|
|
45
|
+
configurable: true,
|
|
46
|
+
enumerable: false,
|
|
47
|
+
get() {
|
|
48
|
+
return this.issues.length === 0;
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
const ZodRealError = /*@__PURE__*/ $constructor("ZodError", initializer, void 0, { Parent: Error });
|
|
53
|
+
//#endregion
|
|
54
|
+
export { ZodRealError };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { _decode, _decodeAsync, _encode, _encodeAsync, _parse, _parseAsync, _safeDecode, _safeDecodeAsync, _safeEncode, _safeEncodeAsync, _safeParse, _safeParseAsync } from "../core/parse.js";
|
|
2
|
+
import { ZodRealError } from "./errors.js";
|
|
3
|
+
//#region node_modules/zod/v4/classic/parse.js
|
|
4
|
+
const parse = /* @__PURE__ */ _parse(ZodRealError);
|
|
5
|
+
const parseAsync = /* @__PURE__ */ _parseAsync(ZodRealError);
|
|
6
|
+
const safeParse = /* @__PURE__ */ _safeParse(ZodRealError);
|
|
7
|
+
const safeParseAsync = /* @__PURE__ */ _safeParseAsync(ZodRealError);
|
|
8
|
+
const encode = /* @__PURE__ */ _encode(ZodRealError);
|
|
9
|
+
const decode = /* @__PURE__ */ _decode(ZodRealError);
|
|
10
|
+
const encodeAsync = /* @__PURE__ */ _encodeAsync(ZodRealError);
|
|
11
|
+
const decodeAsync = /* @__PURE__ */ _decodeAsync(ZodRealError);
|
|
12
|
+
const safeEncode = /* @__PURE__ */ _safeEncode(ZodRealError);
|
|
13
|
+
const safeDecode = /* @__PURE__ */ _safeDecode(ZodRealError);
|
|
14
|
+
const safeEncodeAsync = /* @__PURE__ */ _safeEncodeAsync(ZodRealError);
|
|
15
|
+
const safeDecodeAsync = /* @__PURE__ */ _safeDecodeAsync(ZodRealError);
|
|
16
|
+
//#endregion
|
|
17
|
+
export { decode, decodeAsync, encode, encodeAsync, parse, parseAsync, safeDecode, safeDecodeAsync, safeEncode, safeEncodeAsync, safeParse, safeParseAsync };
|