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/api/legacy.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { toClientEvent } from "../core/events.js";
|
|
2
2
|
import { errors } from "../core/errors.js";
|
|
3
3
|
import { isValidRoomId } from "../core/ids.js";
|
|
4
|
+
import { procedure } from "../http/procedure.js";
|
|
4
5
|
import { paginate, visibleState } from "./rooms.js";
|
|
5
6
|
//#region src/api/legacy.ts
|
|
6
7
|
const viewerOf = (s) => ({
|
|
@@ -83,88 +84,100 @@ async function eventsSince(hs, s, roomIds, from, limit) {
|
|
|
83
84
|
return all.sort((a, b) => a.stream - b.stream).slice(0, limit);
|
|
84
85
|
}
|
|
85
86
|
function registerLegacyRoutes(r) {
|
|
86
|
-
r.client("GET", "/initialSync",
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
"
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
87
|
+
r.client("GET", "/initialSync", procedure({
|
|
88
|
+
auth: "user",
|
|
89
|
+
handler: async (ctx) => {
|
|
90
|
+
const s = ctx.session;
|
|
91
|
+
const hs = ctx.hs;
|
|
92
|
+
const limit = Math.min(Math.max(Number.parseInt(ctx.query.get("limit") ?? "10", 10) || 10, 0), 100);
|
|
93
|
+
const memberships = [
|
|
94
|
+
"join",
|
|
95
|
+
"invite",
|
|
96
|
+
...ctx.query.get("archived") === "true" ? ["leave", "ban"] : []
|
|
97
|
+
];
|
|
98
|
+
const rooms = [];
|
|
99
|
+
for (const membership of memberships) for (const roomId of await hs.rooms.roomsForUser(s.userId, membership)) {
|
|
100
|
+
if (membership === "invite") {
|
|
101
|
+
const invite = await hs.rooms.membershipEvent(roomId, s.userId);
|
|
102
|
+
rooms.push({
|
|
103
|
+
room_id: roomId,
|
|
104
|
+
membership,
|
|
105
|
+
...invite ? { invite: hs.rooms.formatEvent(invite, viewerOf(s)) } : {}
|
|
106
|
+
});
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
rooms.push(await roomInfo(hs, s, roomId, limit));
|
|
111
|
+
} catch {}
|
|
105
112
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
113
|
+
const account = await hs.db.findMany("account_data", { where: [{
|
|
114
|
+
field: "user_id",
|
|
115
|
+
value: s.userId
|
|
116
|
+
}, {
|
|
117
|
+
field: "room_id",
|
|
118
|
+
value: ""
|
|
119
|
+
}] });
|
|
120
|
+
return {
|
|
121
|
+
end: `s${await hs.streams.position()}`,
|
|
122
|
+
rooms,
|
|
123
|
+
presence: [],
|
|
124
|
+
account_data: account.map((a) => ({
|
|
125
|
+
type: a.type,
|
|
126
|
+
content: a.content
|
|
127
|
+
}))
|
|
128
|
+
};
|
|
109
129
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
const end = events.length ? events[events.length - 1].stream : Math.max(from, await hs.streams.position());
|
|
159
|
-
return {
|
|
160
|
-
start: `s${from}`,
|
|
161
|
-
end: `s${end}`,
|
|
162
|
-
chunk: events.map((e) => hs.rooms.formatEvent(e, viewerOf(s), { includeRoomId: true }))
|
|
163
|
-
};
|
|
130
|
+
}));
|
|
131
|
+
r.client("GET", "/rooms/:roomId/initialSync", procedure({
|
|
132
|
+
auth: "user",
|
|
133
|
+
handler: async (ctx) => {
|
|
134
|
+
const s = ctx.session;
|
|
135
|
+
if (!isValidRoomId(ctx.params.roomId)) throw errors.invalidParam("Invalid room ID");
|
|
136
|
+
const limit = Math.min(Math.max(Number.parseInt(ctx.query.get("limit") ?? "10", 10) || 10, 0), 100);
|
|
137
|
+
return roomInfo(ctx.hs, s, ctx.params.roomId, limit);
|
|
138
|
+
}
|
|
139
|
+
}));
|
|
140
|
+
r.client("GET", "/events/:eventId", procedure({
|
|
141
|
+
auth: "user",
|
|
142
|
+
handler: async (ctx) => {
|
|
143
|
+
const s = ctx.session;
|
|
144
|
+
const ev = await ctx.hs.rooms.getEvent(ctx.params.eventId);
|
|
145
|
+
if (!ev || !(await ctx.hs.rooms.filterVisible(s.userId, ev.pdu.room_id, [ev])).length) throw errors.notFound("Event not found");
|
|
146
|
+
return ctx.hs.rooms.formatEvent(ev, viewerOf(s), { includeRoomId: true });
|
|
147
|
+
}
|
|
148
|
+
}));
|
|
149
|
+
r.client("GET", "/events", procedure({
|
|
150
|
+
auth: "user",
|
|
151
|
+
handler: async (ctx) => {
|
|
152
|
+
const s = ctx.session;
|
|
153
|
+
const hs = ctx.hs;
|
|
154
|
+
const now = await hs.streams.position();
|
|
155
|
+
const from = parseStreamToken(ctx.query.get("from"), now);
|
|
156
|
+
const timeout = Math.min(Math.max(Number(ctx.query.get("timeout") ?? 3e4) || 0, 0), hs.config.maxSyncTimeoutMs);
|
|
157
|
+
const peekRoom = ctx.query.get("room_id");
|
|
158
|
+
let roomIds;
|
|
159
|
+
if (peekRoom !== null) {
|
|
160
|
+
if (!isValidRoomId(peekRoom)) throw errors.invalidParam("Invalid room ID");
|
|
161
|
+
const { joined, worldReadable } = await hs.rooms.canPeek(peekRoom, s.userId);
|
|
162
|
+
if (!joined && !worldReadable) throw errors.forbidden("You cannot peek into this room");
|
|
163
|
+
roomIds = [peekRoom];
|
|
164
|
+
} else roomIds = await hs.rooms.roomsForUser(s.userId);
|
|
165
|
+
const deadline = Date.now() + timeout;
|
|
166
|
+
for (;;) {
|
|
167
|
+
const events = await eventsSince(hs, s, roomIds, from, 100);
|
|
168
|
+
const remaining = deadline - Date.now();
|
|
169
|
+
if (events.length || remaining <= 0 || ctx.request.signal.aborted) {
|
|
170
|
+
const end = events.length ? events[events.length - 1].stream : Math.max(from, await hs.streams.position());
|
|
171
|
+
return {
|
|
172
|
+
start: `s${from}`,
|
|
173
|
+
end: `s${end}`,
|
|
174
|
+
chunk: events.map((e) => hs.rooms.formatEvent(e, viewerOf(s), { includeRoomId: true }))
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
await Promise.race([hs.notifier.wait(s.userId, remaining, ctx.request.signal), new Promise((resolve) => setTimeout(resolve, Math.min(remaining, peekRoom !== null ? 1e3 : remaining)).unref?.())]);
|
|
164
178
|
}
|
|
165
|
-
await Promise.race([hs.notifier.wait(s.userId, remaining, ctx.request.signal), new Promise((resolve) => setTimeout(resolve, Math.min(remaining, peekRoom !== null ? 1e3 : remaining)).unref?.())]);
|
|
166
179
|
}
|
|
167
|
-
});
|
|
180
|
+
}));
|
|
168
181
|
}
|
|
169
182
|
//#endregion
|
|
170
183
|
export { registerLegacyRoutes };
|
package/dist/api/media.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { randomString } from "../core/crypto.js";
|
|
2
|
+
import { M } from "../core/m.js";
|
|
2
3
|
import { MatrixError, errors } from "../core/errors.js";
|
|
4
|
+
import { procedure } from "../http/procedure.js";
|
|
3
5
|
//#region src/api/media.ts
|
|
4
6
|
const DEFAULT_MAX_UPLOAD = 52428800;
|
|
5
7
|
const INLINE_TYPES = /^(image\/(png|jpeg|gif|webp|avif|apng)|video\/(mp4|webm|ogg)|audio\/(mpeg|ogg|wav|webm|mp4|aac|flac)|text\/plain)$/;
|
|
@@ -51,7 +53,7 @@ async function remoteMedia(ctx) {
|
|
|
51
53
|
}
|
|
52
54
|
let fetched;
|
|
53
55
|
try {
|
|
54
|
-
fetched = await hs.federation.fetchMedia(serverName, mediaId);
|
|
56
|
+
fetched = await hs.federation.fetchMedia(serverName, mediaId, maxUpload(hs));
|
|
55
57
|
} catch (err) {
|
|
56
58
|
if (err instanceof MatrixError) throw err;
|
|
57
59
|
throw new MatrixError(502, "M_UNKNOWN", "Could not fetch remote media");
|
|
@@ -183,19 +185,25 @@ async function thumbnail(ctx) {
|
|
|
183
185
|
});
|
|
184
186
|
}
|
|
185
187
|
function registerMediaRoutes(r) {
|
|
186
|
-
const config = (ctx) => ({
|
|
187
|
-
r.get("/_matrix/media/v3/config",
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
});
|
|
188
|
+
const config = (ctx) => ({ [M.UPLOAD.SIZE]: maxUpload(ctx.hs) });
|
|
189
|
+
r.get("/_matrix/media/v3/config", procedure({
|
|
190
|
+
auth: "user",
|
|
191
|
+
handler: async (ctx) => {
|
|
192
|
+
return config(ctx);
|
|
193
|
+
}
|
|
194
|
+
}));
|
|
195
|
+
r.get("/_matrix/media/r0/config", procedure({
|
|
196
|
+
auth: "user",
|
|
197
|
+
handler: async (ctx) => {
|
|
198
|
+
return config(ctx);
|
|
199
|
+
}
|
|
200
|
+
}));
|
|
201
|
+
r.get("/_matrix/client/v1/media/config", procedure({
|
|
202
|
+
auth: "user",
|
|
203
|
+
handler: async (ctx) => {
|
|
204
|
+
return config(ctx);
|
|
205
|
+
}
|
|
206
|
+
}));
|
|
199
207
|
const upload = async (ctx) => {
|
|
200
208
|
await ctx.auth();
|
|
201
209
|
await ctx.rateLimit("upload");
|
|
@@ -203,53 +211,61 @@ function registerMediaRoutes(r) {
|
|
|
203
211
|
await storeUpload(ctx, mediaId);
|
|
204
212
|
return { content_uri: `mxc://${ctx.hs.serverName}/${mediaId}` };
|
|
205
213
|
};
|
|
206
|
-
r.post("/_matrix/media/v3/upload", upload);
|
|
207
|
-
r.post("/_matrix/media/r0/upload", upload);
|
|
208
|
-
r.post("/_matrix/media/v1/create",
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
214
|
+
r.post("/_matrix/media/v3/upload", procedure({ handler: upload }));
|
|
215
|
+
r.post("/_matrix/media/r0/upload", procedure({ handler: upload }));
|
|
216
|
+
r.post("/_matrix/media/v1/create", procedure({
|
|
217
|
+
auth: "user",
|
|
218
|
+
handler: async (ctx) => {
|
|
219
|
+
const s = ctx.session;
|
|
220
|
+
const mediaId = randomString(24);
|
|
221
|
+
await ctx.hs.db.create("media", {
|
|
222
|
+
media_id: mediaId,
|
|
223
|
+
content_type: null,
|
|
224
|
+
filename: null,
|
|
225
|
+
size: 0,
|
|
226
|
+
uploader: s.userId,
|
|
227
|
+
created_ts: Date.now(),
|
|
228
|
+
pending: true
|
|
229
|
+
});
|
|
230
|
+
return {
|
|
231
|
+
content_uri: `mxc://${ctx.hs.serverName}/${mediaId}`,
|
|
232
|
+
unused_expires_at: Date.now() + 864e5
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
}));
|
|
236
|
+
r.put("/_matrix/media/v3/upload/:serverName/:mediaId", procedure({
|
|
237
|
+
auth: "user",
|
|
238
|
+
handler: async (ctx) => {
|
|
239
|
+
const s = ctx.session;
|
|
240
|
+
if (ctx.params.serverName !== ctx.hs.serverName) throw errors.notFound("Unknown media");
|
|
241
|
+
const meta = await ctx.hs.db.findOne("media", [{
|
|
242
|
+
field: "media_id",
|
|
243
|
+
value: ctx.params.mediaId
|
|
244
|
+
}]);
|
|
245
|
+
if (!meta) throw errors.notFound("Unknown media");
|
|
246
|
+
if (meta.uploader !== s.userId) throw errors.forbidden("You did not create this media ID");
|
|
247
|
+
if (!meta.pending) throw new MatrixError(409, "M_CANNOT_OVERWRITE_MEDIA", "Media already uploaded");
|
|
248
|
+
await storeUpload(ctx, ctx.params.mediaId);
|
|
249
|
+
return {};
|
|
250
|
+
}
|
|
251
|
+
}));
|
|
238
252
|
for (const prefix of ["/_matrix/media/v3", "/_matrix/media/r0"]) {
|
|
239
|
-
r.get(`${prefix}/download/:serverName/:mediaId`, download);
|
|
240
|
-
r.get(`${prefix}/download/:serverName/:mediaId/:fileName`, download);
|
|
241
|
-
r.get(`${prefix}/thumbnail/:serverName/:mediaId`, thumbnail);
|
|
253
|
+
r.get(`${prefix}/download/:serverName/:mediaId`, procedure({ handler: download }));
|
|
254
|
+
r.get(`${prefix}/download/:serverName/:mediaId/:fileName`, procedure({ handler: download }));
|
|
255
|
+
r.get(`${prefix}/thumbnail/:serverName/:mediaId`, procedure({ handler: thumbnail }));
|
|
242
256
|
}
|
|
243
257
|
const authed = async (ctx) => {
|
|
244
258
|
await ctx.auth();
|
|
245
259
|
return download(ctx);
|
|
246
260
|
};
|
|
247
|
-
r.get("/_matrix/client/v1/media/download/:serverName/:mediaId", authed);
|
|
248
|
-
r.get("/_matrix/client/v1/media/download/:serverName/:mediaId/:fileName", authed);
|
|
249
|
-
r.get("/_matrix/client/v1/media/thumbnail/:serverName/:mediaId",
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
261
|
+
r.get("/_matrix/client/v1/media/download/:serverName/:mediaId", procedure({ handler: authed }));
|
|
262
|
+
r.get("/_matrix/client/v1/media/download/:serverName/:mediaId/:fileName", procedure({ handler: authed }));
|
|
263
|
+
r.get("/_matrix/client/v1/media/thumbnail/:serverName/:mediaId", procedure({
|
|
264
|
+
auth: "user",
|
|
265
|
+
handler: async (ctx) => {
|
|
266
|
+
return thumbnail(ctx);
|
|
267
|
+
}
|
|
268
|
+
}));
|
|
253
269
|
}
|
|
254
270
|
//#endregion
|
|
255
271
|
export { registerMediaRoutes };
|
package/dist/api/misc.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { signJson } from "../core/crypto.js";
|
|
2
|
+
import { M, UNSTABLE } from "../core/m.js";
|
|
2
3
|
import { SUPPORTED_ROOM_VERSIONS } from "../core/events.js";
|
|
3
4
|
import { errors } from "../core/errors.js";
|
|
5
|
+
import { procedure } from "../http/procedure.js";
|
|
4
6
|
import { wellKnown } from "./account.js";
|
|
5
7
|
import { canonicalJsonResponse } from "../http/handler.js";
|
|
8
|
+
import { readFileSync } from "node:fs";
|
|
6
9
|
//#region src/api/misc.ts
|
|
10
|
+
const PACKAGE = JSON.parse(readFileSync(new URL("../../package.json", import.meta.url), "utf8"));
|
|
7
11
|
const SPEC_VERSIONS = [
|
|
8
12
|
"r0.0.1",
|
|
9
13
|
"r0.1.0",
|
|
@@ -26,48 +30,50 @@ const SPEC_VERSIONS = [
|
|
|
26
30
|
"v1.11"
|
|
27
31
|
];
|
|
28
32
|
function registerMiscRoutes(r) {
|
|
29
|
-
r.get("/_matrix/client/versions", () => ({
|
|
33
|
+
r.get("/_matrix/client/versions", procedure({ handler: () => ({
|
|
30
34
|
versions: SPEC_VERSIONS,
|
|
31
35
|
unstable_features: {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
[UNSTABLE.ORG.MATRIX.SIMPLIFIED_MSC3575]: true,
|
|
37
|
+
[UNSTABLE.ORG.MATRIX.MSC3916.STABLE]: true,
|
|
38
|
+
[UNSTABLE.ORG.MATRIX.MSC4140._]: true,
|
|
39
|
+
[UNSTABLE.ORG.MATRIX.MSC4143._]: true,
|
|
40
|
+
[UNSTABLE.ORG.MATRIX.MSC4222._]: true,
|
|
41
|
+
[UNSTABLE.ORG.MATRIX.MSC4306]: true
|
|
38
42
|
}
|
|
39
|
-
}));
|
|
40
|
-
r.get("/.well-known/matrix/client", (ctx) => {
|
|
43
|
+
}) }));
|
|
44
|
+
r.get("/.well-known/matrix/client", procedure({ handler: (ctx) => {
|
|
41
45
|
const wk = wellKnown(ctx.hs);
|
|
42
46
|
if (!wk) throw errors.notFound("No well-known configured");
|
|
43
47
|
return wk;
|
|
44
|
-
});
|
|
45
|
-
r.get("/.well-known/matrix/server", (ctx) => {
|
|
48
|
+
} }));
|
|
49
|
+
r.get("/.well-known/matrix/server", procedure({ handler: (ctx) => {
|
|
46
50
|
if (!ctx.hs.config.federationAddress) throw errors.notFound("No delegation configured");
|
|
47
|
-
return {
|
|
48
|
-
});
|
|
49
|
-
r.get("/.well-known/matrix/support", (ctx) => {
|
|
51
|
+
return { [M.SERVER]: ctx.hs.config.federationAddress };
|
|
52
|
+
} }));
|
|
53
|
+
r.get("/.well-known/matrix/support", procedure({ handler: (ctx) => {
|
|
50
54
|
if (!ctx.hs.config.support) throw errors.notFound("No support information configured");
|
|
51
55
|
return ctx.hs.config.support;
|
|
52
|
-
});
|
|
53
|
-
r.client("GET", "/capabilities",
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
56
|
+
} }));
|
|
57
|
+
r.client("GET", "/capabilities", procedure({
|
|
58
|
+
auth: "user",
|
|
59
|
+
handler: async (ctx) => {
|
|
60
|
+
const available = {};
|
|
61
|
+
for (const v of SUPPORTED_ROOM_VERSIONS) available[v] = /^\d+$/.test(v) ? "stable" : "unstable";
|
|
62
|
+
return { capabilities: {
|
|
63
|
+
[M.CHANGE_PASSWORD]: { enabled: !ctx.hs.oauth && ctx.hs.userAuth.passwordLoginEnabled },
|
|
64
|
+
[M.ROOM_VERSIONS]: {
|
|
65
|
+
default: ctx.hs.config.defaultRoomVersion,
|
|
66
|
+
available
|
|
67
|
+
},
|
|
68
|
+
[M.SET_DISPLAYNAME]: { enabled: true },
|
|
69
|
+
[M.SET_AVATAR_URL]: { enabled: true },
|
|
70
|
+
[M.PROFILE_FIELDS]: { enabled: true },
|
|
71
|
+
[M.THREEPID_CHANGES]: { enabled: ctx.hs.threepids.enabled && !ctx.hs.oauth },
|
|
72
|
+
[M.GET_LOGIN_TOKEN]: { enabled: ctx.hs.config.loginViaExistingSession && !ctx.hs.oauth }
|
|
73
|
+
} };
|
|
74
|
+
}
|
|
75
|
+
}));
|
|
76
|
+
r.get("/_matrix/key/v2/server", procedure({ handler: (ctx) => {
|
|
71
77
|
const hs = ctx.hs;
|
|
72
78
|
const key = hs.signingKey;
|
|
73
79
|
return canonicalJsonResponse(signJson({
|
|
@@ -76,10 +82,13 @@ function registerMiscRoutes(r) {
|
|
|
76
82
|
verify_keys: { [`ed25519:${key.keyId}`]: { key: key.publicKeyBase64 } },
|
|
77
83
|
old_verify_keys: {}
|
|
78
84
|
}, hs.serverName, key));
|
|
79
|
-
});
|
|
80
|
-
r.get("/_matrix/federation/v1/version", (
|
|
81
|
-
|
|
82
|
-
|
|
85
|
+
} }));
|
|
86
|
+
r.get("/_matrix/federation/v1/version", procedure({ handler: (ctx) => {
|
|
87
|
+
if (!ctx.hs.federation.enabled) throw errors.unrecognized("Federation is disabled");
|
|
88
|
+
return { server: {
|
|
89
|
+
name: PACKAGE.name,
|
|
90
|
+
version: PACKAGE.version
|
|
91
|
+
} };
|
|
83
92
|
} }));
|
|
84
93
|
}
|
|
85
94
|
//#endregion
|
package/dist/api/preview.js
CHANGED
|
@@ -1,39 +1,9 @@
|
|
|
1
1
|
import { randomString } from "../core/crypto.js";
|
|
2
|
-
import { errors } from "../core/errors.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { MatrixError, errors } from "../core/errors.js";
|
|
3
|
+
import { publicGet } from "../core/outbound.js";
|
|
4
|
+
import { procedure } from "../http/procedure.js";
|
|
5
5
|
//#region src/api/preview.ts
|
|
6
6
|
const MAX_BYTES = 1048576;
|
|
7
|
-
const privateRanges = new BlockList();
|
|
8
|
-
for (const [net, prefix] of [
|
|
9
|
-
["0.0.0.0", 8],
|
|
10
|
-
["10.0.0.0", 8],
|
|
11
|
-
["100.64.0.0", 10],
|
|
12
|
-
["127.0.0.0", 8],
|
|
13
|
-
["169.254.0.0", 16],
|
|
14
|
-
["172.16.0.0", 12],
|
|
15
|
-
["192.0.0.0", 24],
|
|
16
|
-
["192.168.0.0", 16],
|
|
17
|
-
["198.18.0.0", 15],
|
|
18
|
-
["224.0.0.0", 4],
|
|
19
|
-
["240.0.0.0", 4]
|
|
20
|
-
]) privateRanges.addSubnet(net, prefix, "ipv4");
|
|
21
|
-
for (const [net, prefix] of [
|
|
22
|
-
["::", 128],
|
|
23
|
-
["::1", 128],
|
|
24
|
-
["fc00::", 7],
|
|
25
|
-
["fe80::", 10],
|
|
26
|
-
["ff00::", 8],
|
|
27
|
-
["::ffff:0:0", 96]
|
|
28
|
-
]) privateRanges.addSubnet(net, prefix, "ipv6");
|
|
29
|
-
async function assertPublicHost(hostname) {
|
|
30
|
-
const host = hostname.replace(/^\[|\]$/g, "");
|
|
31
|
-
const addresses = isIP(host) ? [{
|
|
32
|
-
address: host,
|
|
33
|
-
family: isIP(host)
|
|
34
|
-
}] : await lookup(host, { all: true });
|
|
35
|
-
for (const { address, family } of addresses) if (privateRanges.check(address, family === 6 ? "ipv6" : "ipv4")) throw errors.forbidden("URL resolves to a private address");
|
|
36
|
-
}
|
|
37
7
|
function decodeEntities(s) {
|
|
38
8
|
return s.replace(/&(amp|lt|gt|quot|#39|#x27);/g, (m) => ({
|
|
39
9
|
"&": "&",
|
|
@@ -71,42 +41,37 @@ async function preview(hs, rawUrl) {
|
|
|
71
41
|
throw errors.invalidParam("Invalid URL");
|
|
72
42
|
}
|
|
73
43
|
if (url.protocol !== "http:" && url.protocol !== "https:") throw errors.invalidParam("Only http(s) URLs can be previewed");
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
if (
|
|
88
|
-
|
|
89
|
-
|
|
44
|
+
const get = opts.allowPrivateAddresses ? hs.fetch : hs.publicFetch;
|
|
45
|
+
const headers = { "user-agent": "matrix-nodejs-homeserver" };
|
|
46
|
+
const res = await publicGet(get, url, {
|
|
47
|
+
maxBytes: MAX_BYTES * 10,
|
|
48
|
+
truncate: true,
|
|
49
|
+
timeoutMs: 1e4,
|
|
50
|
+
headers
|
|
51
|
+
}).catch((err) => {
|
|
52
|
+
throw err instanceof MatrixError ? err : errors.notFound("Could not fetch URL");
|
|
53
|
+
});
|
|
54
|
+
if (res.status !== 200) throw errors.notFound("Could not fetch URL");
|
|
55
|
+
const type = res.headers.get("content-type") ?? "";
|
|
56
|
+
if (type.startsWith("image/")) {
|
|
57
|
+
if (res.body.length >= MAX_BYTES * 10) return {};
|
|
58
|
+
return {
|
|
59
|
+
...await storeImage(hs, res.body, type),
|
|
60
|
+
"og:url": res.url.href
|
|
90
61
|
};
|
|
91
|
-
if (!type.includes("html")) return {};
|
|
92
|
-
let size = 0;
|
|
93
|
-
const chunks = [];
|
|
94
|
-
for await (const chunk of res.body) {
|
|
95
|
-
size += chunk.length;
|
|
96
|
-
if (size > MAX_BYTES) break;
|
|
97
|
-
chunks.push(chunk);
|
|
98
|
-
}
|
|
99
|
-
const og = parseOpenGraph(Buffer.concat(chunks).toString("utf8"));
|
|
100
|
-
if (og["og:image"]) try {
|
|
101
|
-
const imageUrl = new URL(og["og:image"], url);
|
|
102
|
-
if (!opts.allowPrivateAddresses) await assertPublicHost(imageUrl.hostname);
|
|
103
|
-
const img = await fetch(imageUrl, { signal: AbortSignal.timeout(1e4) });
|
|
104
|
-
const imgType = img.headers.get("content-type") ?? "";
|
|
105
|
-
if (img.ok && imgType.startsWith("image/")) Object.assign(og, await storeImage(hs, img, imgType));
|
|
106
|
-
} catch {}
|
|
107
|
-
return og;
|
|
108
62
|
}
|
|
109
|
-
|
|
63
|
+
if (!type.includes("html")) return {};
|
|
64
|
+
const og = parseOpenGraph(res.body.subarray(0, MAX_BYTES).toString("utf8"));
|
|
65
|
+
if (og["og:image"]) try {
|
|
66
|
+
const img = await publicGet(get, new URL(og["og:image"], res.url), {
|
|
67
|
+
maxBytes: MAX_BYTES * 10,
|
|
68
|
+
timeoutMs: 1e4,
|
|
69
|
+
headers
|
|
70
|
+
});
|
|
71
|
+
const imgType = img.headers.get("content-type") ?? "";
|
|
72
|
+
if (img.status === 200 && imgType.startsWith("image/")) Object.assign(og, await storeImage(hs, img.body, imgType));
|
|
73
|
+
} catch {}
|
|
74
|
+
return og;
|
|
110
75
|
}
|
|
111
76
|
/** Read width/height from PNG, GIF, JPEG or WebP headers. */
|
|
112
77
|
function imageSize(data) {
|
|
@@ -156,9 +121,8 @@ function imageSize(data) {
|
|
|
156
121
|
return null;
|
|
157
122
|
}
|
|
158
123
|
/** Store a fetched image in the media repository and describe it for a preview. */
|
|
159
|
-
async function storeImage(hs,
|
|
160
|
-
const data = new Uint8Array(
|
|
161
|
-
if (data.length > MAX_BYTES * 10) return {};
|
|
124
|
+
async function storeImage(hs, body, type) {
|
|
125
|
+
const data = new Uint8Array(body);
|
|
162
126
|
const mediaId = randomString(24);
|
|
163
127
|
await hs.media.put(mediaId, data, {
|
|
164
128
|
contentType: type,
|
|
@@ -192,9 +156,9 @@ function registerPreviewRoutes(r) {
|
|
|
192
156
|
if (!url) throw errors.missingParam("Missing url");
|
|
193
157
|
return preview(ctx.hs, url);
|
|
194
158
|
};
|
|
195
|
-
r.get("/_matrix/media/v3/preview_url", handler);
|
|
196
|
-
r.get("/_matrix/media/r0/preview_url", handler);
|
|
197
|
-
r.get("/_matrix/client/v1/media/preview_url", handler);
|
|
159
|
+
r.get("/_matrix/media/v3/preview_url", procedure({ handler }));
|
|
160
|
+
r.get("/_matrix/media/r0/preview_url", procedure({ handler }));
|
|
161
|
+
r.get("/_matrix/client/v1/media/preview_url", procedure({ handler }));
|
|
198
162
|
}
|
|
199
163
|
//#endregion
|
|
200
164
|
export { imageSize, parseOpenGraph, registerPreviewRoutes };
|