matrix-nodejs-homeserver 0.0.0-stage → 0.2.0
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/LICENSE +75 -0
- package/README.md +457 -2
- package/dist/adapters/auth.d.ts +50 -0
- package/dist/adapters/auth.js +18 -0
- package/dist/adapters/cluster.d.ts +55 -0
- package/dist/adapters/cluster.js +249 -0
- package/dist/adapters/media.d.ts +50 -0
- package/dist/adapters/media.js +63 -0
- package/dist/adapters/s3.d.ts +33 -0
- package/dist/adapters/s3.js +95 -0
- package/dist/api/account.js +560 -0
- package/dist/api/admin.js +251 -0
- package/dist/api/appservice.js +33 -0
- package/dist/api/backup.js +269 -0
- package/dist/api/federation.js +792 -0
- package/dist/api/filters.js +87 -0
- package/dist/api/index.js +47 -0
- package/dist/api/legacy.js +170 -0
- package/dist/api/media.js +255 -0
- package/dist/api/misc.js +86 -0
- package/dist/api/preview.js +200 -0
- package/dist/api/profile.js +165 -0
- package/dist/api/push.js +187 -0
- package/dist/api/relationships.js +264 -0
- package/dist/api/rooms.js +1364 -0
- package/dist/api/rtc.d.ts +24 -0
- package/dist/api/rtc.js +52 -0
- package/dist/api/search.js +155 -0
- package/dist/api/slidingsync.js +464 -0
- package/dist/api/spaces.js +172 -0
- package/dist/api/sync.js +600 -0
- package/dist/api/threadsubs.js +129 -0
- package/dist/api/threepid.js +122 -0
- package/dist/api/uia.js +126 -0
- package/dist/api/user.js +286 -0
- package/dist/core/admin.d.ts +85 -0
- package/dist/core/admin.js +307 -0
- package/dist/core/adminroom.d.ts +49 -0
- package/dist/core/adminroom.js +482 -0
- package/dist/core/appservices.d.ts +73 -0
- package/dist/core/appservices.js +368 -0
- package/dist/core/audit.d.ts +51 -0
- package/dist/core/audit.js +86 -0
- package/dist/core/auth.d.ts +48 -0
- package/dist/core/auth.js +237 -0
- package/dist/core/authrules.d.ts +4 -0
- package/dist/core/authrules.js +347 -0
- package/dist/core/cluster.d.ts +66 -0
- package/dist/core/cluster.js +191 -0
- package/dist/core/crypto.d.ts +10 -0
- package/dist/core/crypto.js +100 -0
- package/dist/core/delayed.d.ts +39 -0
- package/dist/core/delayed.js +159 -0
- package/dist/core/errors.d.ts +9 -0
- package/dist/core/errors.js +45 -0
- package/dist/core/events.d.ts +32 -0
- package/dist/core/events.js +185 -0
- package/dist/core/glob.js +66 -0
- package/dist/core/identity.d.ts +24 -0
- package/dist/core/identity.js +96 -0
- package/dist/core/ids.js +36 -0
- package/dist/core/invitefilter.js +35 -0
- package/dist/core/json.js +16 -0
- package/dist/core/keys.d.ts +36 -0
- package/dist/core/keys.js +449 -0
- package/dist/core/notifier.d.ts +17 -0
- package/dist/core/notifier.js +54 -0
- package/dist/core/oauth.d.ts +38 -0
- package/dist/core/oauth.js +108 -0
- package/dist/core/partialstate.d.ts +32 -0
- package/dist/core/partialstate.js +197 -0
- package/dist/core/phone.js +268 -0
- package/dist/core/policy.d.ts +29 -0
- package/dist/core/policy.js +96 -0
- package/dist/core/profile.js +39 -0
- package/dist/core/push.d.ts +47 -0
- package/dist/core/push.js +306 -0
- package/dist/core/pushrules.d.ts +1 -0
- package/dist/core/pushrules.js +228 -0
- package/dist/core/ratelimit.d.ts +23 -0
- package/dist/core/ratelimit.js +77 -0
- package/dist/core/rooms.d.ts +215 -0
- package/dist/core/rooms.js +1729 -0
- package/dist/core/settings.d.ts +11 -0
- package/dist/core/settings.js +29 -0
- package/dist/core/sso.d.ts +46 -0
- package/dist/core/sso.js +165 -0
- package/dist/core/stateres.d.ts +4 -0
- package/dist/core/stateres.js +208 -0
- package/dist/core/threepid.d.ts +80 -0
- package/dist/core/threepid.js +275 -0
- package/dist/core/typing.d.ts +16 -0
- package/dist/core/typing.js +60 -0
- package/dist/federation/client.d.ts +51 -0
- package/dist/federation/client.js +278 -0
- package/dist/federation/index.d.ts +153 -0
- package/dist/federation/index.js +1058 -0
- package/dist/federation/keyring.d.ts +26 -0
- package/dist/federation/keyring.js +122 -0
- package/dist/federation/sender.d.ts +26 -0
- package/dist/federation/sender.js +164 -0
- package/dist/homeserver.d.ts +223 -0
- package/dist/homeserver.js +301 -0
- package/dist/http/adapters.d.ts +40 -0
- package/dist/http/adapters.js +130 -0
- package/dist/http/handler.d.ts +8 -0
- package/dist/http/handler.js +202 -0
- package/dist/http/input.js +24 -0
- package/dist/http/router.d.ts +60 -0
- package/dist/http/router.js +76 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +14 -0
- package/dist/storage/memory.d.ts +5 -0
- package/dist/storage/memory.js +122 -0
- package/dist/storage/schema.d.ts +4 -0
- package/dist/storage/schema.js +664 -0
- package/dist/storage/sql.d.ts +72 -0
- package/dist/storage/sql.js +274 -0
- package/dist/storage/types.d.ts +55 -0
- package/package.json +67 -4
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { eventIdOf, roomVersionFeatures, stateKey } from "./events.js";
|
|
2
|
+
import { EventEmitter } from "node:events";
|
|
3
|
+
//#region src/core/partialstate.ts
|
|
4
|
+
const RESYNC_CLAIM_MS = 6e5;
|
|
5
|
+
var PartialStateService = class {
|
|
6
|
+
hs;
|
|
7
|
+
partial = /* @__PURE__ */ new Map();
|
|
8
|
+
completed = new EventEmitter();
|
|
9
|
+
running = /* @__PURE__ */ new Set();
|
|
10
|
+
completedStreams = /* @__PURE__ */ new Map();
|
|
11
|
+
stopped = false;
|
|
12
|
+
constructor(hs) {
|
|
13
|
+
this.hs = hs;
|
|
14
|
+
this.completed.setMaxListeners(0);
|
|
15
|
+
hs.cluster.subscribe("partial-state", (msg) => {
|
|
16
|
+
if (typeof msg?.roomId !== "string") return;
|
|
17
|
+
this.partial.delete(msg.roomId);
|
|
18
|
+
this.completedStreams.delete(msg.roomId);
|
|
19
|
+
this.completed.emit(msg.roomId);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
async isPartial(roomId) {
|
|
23
|
+
const cached = this.partial.get(roomId);
|
|
24
|
+
if (cached !== void 0) return cached;
|
|
25
|
+
const row = await this.hs.db.findOne("partial_state_rooms", [{
|
|
26
|
+
field: "room_id",
|
|
27
|
+
value: roomId
|
|
28
|
+
}]);
|
|
29
|
+
const partial = !!row && row.completed_stream === null;
|
|
30
|
+
if (this.partial.size > 1e4) this.partial.clear();
|
|
31
|
+
this.partial.set(roomId, partial);
|
|
32
|
+
return partial;
|
|
33
|
+
}
|
|
34
|
+
/** Servers the joining server told us were in the room (used as destinations and resync sources). */
|
|
35
|
+
async servers(roomId) {
|
|
36
|
+
if (!await this.isPartial(roomId)) return [];
|
|
37
|
+
const row = await this.hs.db.findOne("partial_state_rooms", [{
|
|
38
|
+
field: "room_id",
|
|
39
|
+
value: roomId
|
|
40
|
+
}]);
|
|
41
|
+
return Array.isArray(row?.servers) ? row.servers : [];
|
|
42
|
+
}
|
|
43
|
+
/** Stream position at which the room got its full state (null if it never was partial). */
|
|
44
|
+
async completedAt(roomId) {
|
|
45
|
+
if (this.partial.get(roomId) === false && this.completedStreams.has(roomId)) return this.completedStreams.get(roomId);
|
|
46
|
+
const at = (await this.hs.db.findOne("partial_state_rooms", [{
|
|
47
|
+
field: "room_id",
|
|
48
|
+
value: roomId
|
|
49
|
+
}]))?.completed_stream ?? null;
|
|
50
|
+
if (this.completedStreams.size > 1e4) this.completedStreams.clear();
|
|
51
|
+
this.completedStreams.set(roomId, at);
|
|
52
|
+
return at;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* A device list update for a user we do not share a (known) room with: if they may be a member
|
|
56
|
+
* of a partial-state room hosted by the sender, remember them for when the state is complete.
|
|
57
|
+
*/
|
|
58
|
+
async notePendingDeviceUpdate(userId, origin) {
|
|
59
|
+
const rows = (await this.hs.db.findMany("partial_state_rooms", {})).filter((r) => r.completed_stream === null && (r.servers ?? []).includes(origin));
|
|
60
|
+
for (const row of rows) {
|
|
61
|
+
const pending = Array.isArray(row.pending_device_users) ? row.pending_device_users : [];
|
|
62
|
+
if (pending.includes(userId)) continue;
|
|
63
|
+
await this.hs.db.update("partial_state_rooms", [{
|
|
64
|
+
field: "room_id",
|
|
65
|
+
value: row.room_id
|
|
66
|
+
}], { pending_device_users: [...pending, userId] });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
async markPartial(roomId, joinEventId, origin, servers) {
|
|
70
|
+
await this.hs.db.upsert("partial_state_rooms", {
|
|
71
|
+
room_id: roomId,
|
|
72
|
+
join_event_id: joinEventId,
|
|
73
|
+
origin,
|
|
74
|
+
servers: [.../* @__PURE__ */ new Set([origin, ...servers])],
|
|
75
|
+
created_ts: Date.now(),
|
|
76
|
+
completed_stream: null,
|
|
77
|
+
pending_device_users: []
|
|
78
|
+
});
|
|
79
|
+
this.partial.set(roomId, true);
|
|
80
|
+
this.completedStreams.delete(roomId);
|
|
81
|
+
this.hs.cluster.publish("partial-state", { roomId }).catch(() => {});
|
|
82
|
+
this.startResync(roomId);
|
|
83
|
+
}
|
|
84
|
+
/** Resolve once the room has its full state (or the signal aborts / the server closes). */
|
|
85
|
+
async waitForFullState(roomId, signal) {
|
|
86
|
+
if (!await this.isPartial(roomId)) return;
|
|
87
|
+
await new Promise((resolve) => {
|
|
88
|
+
const done = () => {
|
|
89
|
+
clearInterval(poll);
|
|
90
|
+
this.completed.off(roomId, check);
|
|
91
|
+
signal?.removeEventListener("abort", done);
|
|
92
|
+
this.hs.off?.("close", done);
|
|
93
|
+
resolve();
|
|
94
|
+
};
|
|
95
|
+
const check = () => void this.isPartial(roomId).then((p) => {
|
|
96
|
+
if (!p) done();
|
|
97
|
+
}).catch(() => {});
|
|
98
|
+
this.completed.on(roomId, check);
|
|
99
|
+
signal?.addEventListener("abort", done);
|
|
100
|
+
this.hs.once?.("close", done);
|
|
101
|
+
const poll = setInterval(() => {
|
|
102
|
+
this.partial.delete(roomId);
|
|
103
|
+
check();
|
|
104
|
+
}, 5e3);
|
|
105
|
+
poll.unref?.();
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
/** Resume resyncs of rooms left partial (e.g. by a restart). */
|
|
109
|
+
async resume() {
|
|
110
|
+
const rows = (await this.hs.db.findMany("partial_state_rooms", {})).filter((r) => r.completed_stream === null);
|
|
111
|
+
this.hs.logger.debug("Resuming partial state resyncs", rows.map((r) => r.room_id));
|
|
112
|
+
for (const row of rows) this.startResync(row.room_id);
|
|
113
|
+
}
|
|
114
|
+
stop() {
|
|
115
|
+
this.stopped = true;
|
|
116
|
+
}
|
|
117
|
+
startResync(roomId) {
|
|
118
|
+
if (this.running.has(roomId)) return;
|
|
119
|
+
this.running.add(roomId);
|
|
120
|
+
this.resync(roomId).catch((err) => this.hs.logger.warn("Partial state resync failed", roomId, err)).finally(() => this.running.delete(roomId));
|
|
121
|
+
}
|
|
122
|
+
async resync(roomId) {
|
|
123
|
+
const claim = `partial-resync:${roomId}`;
|
|
124
|
+
if (!await this.hs.cluster.setIfAbsent(claim, this.hs.instanceId, RESYNC_CLAIM_MS)) {
|
|
125
|
+
this.hs.logger.debug("Partial state resync of", roomId, "is claimed elsewhere");
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
this.hs.logger.debug("Partial state resync of", roomId, "started");
|
|
129
|
+
try {
|
|
130
|
+
let delay = 1e3;
|
|
131
|
+
for (let attempt = 0; !this.stopped && !this.hs.isClosed; attempt++) {
|
|
132
|
+
const row = await this.hs.db.findOne("partial_state_rooms", [{
|
|
133
|
+
field: "room_id",
|
|
134
|
+
value: roomId
|
|
135
|
+
}]);
|
|
136
|
+
if (!row || row.completed_stream !== null) return;
|
|
137
|
+
if (await this.tryResync(roomId, row)) return;
|
|
138
|
+
if (this.stopped || this.hs.isClosed) return;
|
|
139
|
+
if (!await this.hs.rooms.serverIsJoined(roomId)) {
|
|
140
|
+
await this.finish(roomId);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
await new Promise((r) => setTimeout(r, delay).unref?.());
|
|
144
|
+
delay = Math.min(delay * 2, 6e4);
|
|
145
|
+
await this.hs.cluster.set(claim, this.hs.instanceId, RESYNC_CLAIM_MS);
|
|
146
|
+
}
|
|
147
|
+
} finally {
|
|
148
|
+
await this.hs.cluster.delete(claim).catch(() => {});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
async tryResync(roomId, row) {
|
|
152
|
+
const version = await this.hs.rooms.roomVersion(roomId);
|
|
153
|
+
const join = await this.hs.rooms.getEvent(row.join_event_id);
|
|
154
|
+
if (!version || !join) {
|
|
155
|
+
await this.finish(roomId);
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
const prevIds = (join.pdu.prev_events ?? []).map(eventIdOf);
|
|
159
|
+
const servers = [.../* @__PURE__ */ new Set([row.origin, ...row.servers ?? []])].filter((s) => s && s !== this.hs.serverName);
|
|
160
|
+
for (const server of servers) {
|
|
161
|
+
if (this.stopped || this.hs.isClosed) return true;
|
|
162
|
+
try {
|
|
163
|
+
const fullBefore = await this.hs.federation.stateFromPrevs(server, roomId, version, prevIds, { remote: true });
|
|
164
|
+
if (this.stopped || this.hs.isClosed) return true;
|
|
165
|
+
if (!fullBefore[stateKey("m.room.create", "")] && !roomVersionFeatures(version).v12) throw new Error("Resynced state lacks the create event");
|
|
166
|
+
const notified = /* @__PURE__ */ new Set([...row.servers ?? [], ...this.hs.federation.serversInRoom(await this.hs.rooms.currentState(roomId), false)]);
|
|
167
|
+
const missed = await this.hs.rooms.withLock(roomId, () => this.hs.rooms.completePartialState(roomId, version, row.join_event_id, fullBefore, notified));
|
|
168
|
+
await this.finish(roomId);
|
|
169
|
+
if (missed.length) for (const userId of (await this.hs.rooms.joinedMembers(roomId)).filter((u) => this.hs.isLocalUser(u))) await this.hs.federation.onDeviceListChange(userId, void 0, new Set(missed)).catch(() => {});
|
|
170
|
+
return true;
|
|
171
|
+
} catch (err) {
|
|
172
|
+
this.hs.logger.debug("Partial state resync from", server, "failed", err);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
async finish(roomId) {
|
|
178
|
+
await this.hs.streams.write((stream) => this.hs.db.update("partial_state_rooms", [{
|
|
179
|
+
field: "room_id",
|
|
180
|
+
value: roomId
|
|
181
|
+
}], { completed_stream: stream }));
|
|
182
|
+
this.partial.set(roomId, false);
|
|
183
|
+
this.completedStreams.delete(roomId);
|
|
184
|
+
this.completed.emit(roomId);
|
|
185
|
+
this.hs.cluster.publish("partial-state", { roomId }).catch(() => {});
|
|
186
|
+
const members = await this.hs.rooms.joinedMembers(roomId);
|
|
187
|
+
this.hs.notifier.notify(members.filter((u) => this.hs.isLocalUser(u)));
|
|
188
|
+
const row = await this.hs.db.findOne("partial_state_rooms", [{
|
|
189
|
+
field: "room_id",
|
|
190
|
+
value: roomId
|
|
191
|
+
}]);
|
|
192
|
+
const pending = Array.isArray(row?.pending_device_users) ? row.pending_device_users : [];
|
|
193
|
+
await this.hs.keys.onPartialStateComplete(roomId, pending).catch((err) => this.hs.logger.warn("Device list refresh failed", err));
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
//#endregion
|
|
197
|
+
export { PartialStateService };
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
//#region src/core/phone.ts
|
|
2
|
+
/** Country calling codes by ISO 3166-1 alpha-2 code. */
|
|
3
|
+
const CALLING_CODES = {
|
|
4
|
+
AD: "376",
|
|
5
|
+
AE: "971",
|
|
6
|
+
AF: "93",
|
|
7
|
+
AG: "1",
|
|
8
|
+
AI: "1",
|
|
9
|
+
AL: "355",
|
|
10
|
+
AM: "374",
|
|
11
|
+
AO: "244",
|
|
12
|
+
AR: "54",
|
|
13
|
+
AS: "1",
|
|
14
|
+
AT: "43",
|
|
15
|
+
AU: "61",
|
|
16
|
+
AW: "297",
|
|
17
|
+
AX: "358",
|
|
18
|
+
AZ: "994",
|
|
19
|
+
BA: "387",
|
|
20
|
+
BB: "1",
|
|
21
|
+
BD: "880",
|
|
22
|
+
BE: "32",
|
|
23
|
+
BF: "226",
|
|
24
|
+
BG: "359",
|
|
25
|
+
BH: "973",
|
|
26
|
+
BI: "257",
|
|
27
|
+
BJ: "229",
|
|
28
|
+
BL: "590",
|
|
29
|
+
BM: "1",
|
|
30
|
+
BN: "673",
|
|
31
|
+
BO: "591",
|
|
32
|
+
BQ: "599",
|
|
33
|
+
BR: "55",
|
|
34
|
+
BS: "1",
|
|
35
|
+
BT: "975",
|
|
36
|
+
BW: "267",
|
|
37
|
+
BY: "375",
|
|
38
|
+
BZ: "501",
|
|
39
|
+
CA: "1",
|
|
40
|
+
CC: "61",
|
|
41
|
+
CD: "243",
|
|
42
|
+
CF: "236",
|
|
43
|
+
CG: "242",
|
|
44
|
+
CH: "41",
|
|
45
|
+
CI: "225",
|
|
46
|
+
CK: "682",
|
|
47
|
+
CL: "56",
|
|
48
|
+
CM: "237",
|
|
49
|
+
CN: "86",
|
|
50
|
+
CO: "57",
|
|
51
|
+
CR: "506",
|
|
52
|
+
CU: "53",
|
|
53
|
+
CV: "238",
|
|
54
|
+
CW: "599",
|
|
55
|
+
CX: "61",
|
|
56
|
+
CY: "357",
|
|
57
|
+
CZ: "420",
|
|
58
|
+
DE: "49",
|
|
59
|
+
DJ: "253",
|
|
60
|
+
DK: "45",
|
|
61
|
+
DM: "1",
|
|
62
|
+
DO: "1",
|
|
63
|
+
DZ: "213",
|
|
64
|
+
EC: "593",
|
|
65
|
+
EE: "372",
|
|
66
|
+
EG: "20",
|
|
67
|
+
EH: "212",
|
|
68
|
+
ER: "291",
|
|
69
|
+
ES: "34",
|
|
70
|
+
ET: "251",
|
|
71
|
+
FI: "358",
|
|
72
|
+
FJ: "679",
|
|
73
|
+
FK: "500",
|
|
74
|
+
FM: "691",
|
|
75
|
+
FO: "298",
|
|
76
|
+
FR: "33",
|
|
77
|
+
GA: "241",
|
|
78
|
+
GB: "44",
|
|
79
|
+
GD: "1",
|
|
80
|
+
GE: "995",
|
|
81
|
+
GF: "594",
|
|
82
|
+
GG: "44",
|
|
83
|
+
GH: "233",
|
|
84
|
+
GI: "350",
|
|
85
|
+
GL: "299",
|
|
86
|
+
GM: "220",
|
|
87
|
+
GN: "224",
|
|
88
|
+
GP: "590",
|
|
89
|
+
GQ: "240",
|
|
90
|
+
GR: "30",
|
|
91
|
+
GT: "502",
|
|
92
|
+
GU: "1",
|
|
93
|
+
GW: "245",
|
|
94
|
+
GY: "592",
|
|
95
|
+
HK: "852",
|
|
96
|
+
HN: "504",
|
|
97
|
+
HR: "385",
|
|
98
|
+
HT: "509",
|
|
99
|
+
HU: "36",
|
|
100
|
+
ID: "62",
|
|
101
|
+
IE: "353",
|
|
102
|
+
IL: "972",
|
|
103
|
+
IM: "44",
|
|
104
|
+
IN: "91",
|
|
105
|
+
IO: "246",
|
|
106
|
+
IQ: "964",
|
|
107
|
+
IR: "98",
|
|
108
|
+
IS: "354",
|
|
109
|
+
IT: "39",
|
|
110
|
+
JE: "44",
|
|
111
|
+
JM: "1",
|
|
112
|
+
JO: "962",
|
|
113
|
+
JP: "81",
|
|
114
|
+
KE: "254",
|
|
115
|
+
KG: "996",
|
|
116
|
+
KH: "855",
|
|
117
|
+
KI: "686",
|
|
118
|
+
KM: "269",
|
|
119
|
+
KN: "1",
|
|
120
|
+
KP: "850",
|
|
121
|
+
KR: "82",
|
|
122
|
+
KW: "965",
|
|
123
|
+
KY: "1",
|
|
124
|
+
KZ: "7",
|
|
125
|
+
LA: "856",
|
|
126
|
+
LB: "961",
|
|
127
|
+
LC: "1",
|
|
128
|
+
LI: "423",
|
|
129
|
+
LK: "94",
|
|
130
|
+
LR: "231",
|
|
131
|
+
LS: "266",
|
|
132
|
+
LT: "370",
|
|
133
|
+
LU: "352",
|
|
134
|
+
LV: "371",
|
|
135
|
+
LY: "218",
|
|
136
|
+
MA: "212",
|
|
137
|
+
MC: "377",
|
|
138
|
+
MD: "373",
|
|
139
|
+
ME: "382",
|
|
140
|
+
MF: "590",
|
|
141
|
+
MG: "261",
|
|
142
|
+
MH: "692",
|
|
143
|
+
MK: "389",
|
|
144
|
+
ML: "223",
|
|
145
|
+
MM: "95",
|
|
146
|
+
MN: "976",
|
|
147
|
+
MO: "853",
|
|
148
|
+
MP: "1",
|
|
149
|
+
MQ: "596",
|
|
150
|
+
MR: "222",
|
|
151
|
+
MS: "1",
|
|
152
|
+
MT: "356",
|
|
153
|
+
MU: "230",
|
|
154
|
+
MV: "960",
|
|
155
|
+
MW: "265",
|
|
156
|
+
MX: "52",
|
|
157
|
+
MY: "60",
|
|
158
|
+
MZ: "258",
|
|
159
|
+
NA: "264",
|
|
160
|
+
NC: "687",
|
|
161
|
+
NE: "227",
|
|
162
|
+
NF: "672",
|
|
163
|
+
NG: "234",
|
|
164
|
+
NI: "505",
|
|
165
|
+
NL: "31",
|
|
166
|
+
NO: "47",
|
|
167
|
+
NP: "977",
|
|
168
|
+
NR: "674",
|
|
169
|
+
NU: "683",
|
|
170
|
+
NZ: "64",
|
|
171
|
+
OM: "968",
|
|
172
|
+
PA: "507",
|
|
173
|
+
PE: "51",
|
|
174
|
+
PF: "689",
|
|
175
|
+
PG: "675",
|
|
176
|
+
PH: "63",
|
|
177
|
+
PK: "92",
|
|
178
|
+
PL: "48",
|
|
179
|
+
PM: "508",
|
|
180
|
+
PR: "1",
|
|
181
|
+
PS: "970",
|
|
182
|
+
PT: "351",
|
|
183
|
+
PW: "680",
|
|
184
|
+
PY: "595",
|
|
185
|
+
QA: "974",
|
|
186
|
+
RE: "262",
|
|
187
|
+
RO: "40",
|
|
188
|
+
RS: "381",
|
|
189
|
+
RU: "7",
|
|
190
|
+
RW: "250",
|
|
191
|
+
SA: "966",
|
|
192
|
+
SB: "677",
|
|
193
|
+
SC: "248",
|
|
194
|
+
SD: "249",
|
|
195
|
+
SE: "46",
|
|
196
|
+
SG: "65",
|
|
197
|
+
SH: "290",
|
|
198
|
+
SI: "386",
|
|
199
|
+
SJ: "47",
|
|
200
|
+
SK: "421",
|
|
201
|
+
SL: "232",
|
|
202
|
+
SM: "378",
|
|
203
|
+
SN: "221",
|
|
204
|
+
SO: "252",
|
|
205
|
+
SR: "597",
|
|
206
|
+
SS: "211",
|
|
207
|
+
ST: "239",
|
|
208
|
+
SV: "503",
|
|
209
|
+
SX: "1",
|
|
210
|
+
SY: "963",
|
|
211
|
+
SZ: "268",
|
|
212
|
+
TC: "1",
|
|
213
|
+
TD: "235",
|
|
214
|
+
TG: "228",
|
|
215
|
+
TH: "66",
|
|
216
|
+
TJ: "992",
|
|
217
|
+
TK: "690",
|
|
218
|
+
TL: "670",
|
|
219
|
+
TM: "993",
|
|
220
|
+
TN: "216",
|
|
221
|
+
TO: "676",
|
|
222
|
+
TR: "90",
|
|
223
|
+
TT: "1",
|
|
224
|
+
TV: "688",
|
|
225
|
+
TW: "886",
|
|
226
|
+
TZ: "255",
|
|
227
|
+
UA: "380",
|
|
228
|
+
UG: "256",
|
|
229
|
+
US: "1",
|
|
230
|
+
UY: "598",
|
|
231
|
+
UZ: "998",
|
|
232
|
+
VA: "39",
|
|
233
|
+
VC: "1",
|
|
234
|
+
VE: "58",
|
|
235
|
+
VG: "1",
|
|
236
|
+
VI: "1",
|
|
237
|
+
VN: "84",
|
|
238
|
+
VU: "678",
|
|
239
|
+
WF: "681",
|
|
240
|
+
WS: "685",
|
|
241
|
+
XK: "383",
|
|
242
|
+
YE: "967",
|
|
243
|
+
YT: "262",
|
|
244
|
+
ZA: "27",
|
|
245
|
+
ZM: "260",
|
|
246
|
+
ZW: "263"
|
|
247
|
+
};
|
|
248
|
+
/**
|
|
249
|
+
* The MSISDN for a phone number entered in a country (`country` is an ISO 3166-1 alpha-2 code).
|
|
250
|
+
* Numbers given in international form ("+48 ...", "0048 ...") keep their own country code.
|
|
251
|
+
* Returns null for numbers that cannot be valid.
|
|
252
|
+
*/
|
|
253
|
+
function toMsisdn(country, phoneNumber) {
|
|
254
|
+
let digits = phoneNumber.trim().replace(/[\s().\-/]/g, "");
|
|
255
|
+
if (!/^\+?\d+$/.test(digits)) return null;
|
|
256
|
+
let msisdn;
|
|
257
|
+
if (digits.startsWith("+")) msisdn = digits.slice(1);
|
|
258
|
+
else if (digits.startsWith("00")) msisdn = digits.slice(2);
|
|
259
|
+
else {
|
|
260
|
+
const code = CALLING_CODES[country.toUpperCase()];
|
|
261
|
+
if (!code) return null;
|
|
262
|
+
if (digits.startsWith("0") && code !== "39") digits = digits.replace(/^0+/, "");
|
|
263
|
+
msisdn = code + digits;
|
|
264
|
+
}
|
|
265
|
+
return msisdn.length >= 7 && msisdn.length <= 15 ? msisdn : null;
|
|
266
|
+
}
|
|
267
|
+
//#endregion
|
|
268
|
+
export { toMsisdn };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { PDU } from "./events.js";
|
|
2
|
+
import { Homeserver } from "../homeserver.js";
|
|
3
|
+
import { KeyObject } from "node:crypto";
|
|
4
|
+
//#region src/core/policy.d.ts
|
|
5
|
+
interface RoomPolicy {
|
|
6
|
+
via: string;
|
|
7
|
+
key: KeyObject;
|
|
8
|
+
}
|
|
9
|
+
export declare class PolicyServers {
|
|
10
|
+
private readonly hs;
|
|
11
|
+
constructor(hs: Homeserver);
|
|
12
|
+
/** The room's policy server, if the room uses one (valid `m.room.policy` and a joined user on `via`). */
|
|
13
|
+
policyOf(roomId: string): Promise<RoomPolicy | null>;
|
|
14
|
+
/** The policy state event itself is never sent to the policy server. */
|
|
15
|
+
private exempt;
|
|
16
|
+
private validSignature;
|
|
17
|
+
/** Ask the policy server to sign `pdu`; adds the signature to it. Throws the server's error. */
|
|
18
|
+
private requestSignature;
|
|
19
|
+
/**
|
|
20
|
+
* Before a local event is sent: get the policy server's signature, or refuse the event with the
|
|
21
|
+
* policy server's error. If the policy server cannot be reached the event is sent unsigned (other
|
|
22
|
+
* servers may then soft-fail it), so an unavailable policy server does not lock up the room.
|
|
23
|
+
*/
|
|
24
|
+
signLocal(roomId: string, version: string, pdu: PDU): Promise<void>;
|
|
25
|
+
/** Whether a remote event is recommended for inclusion (otherwise it is soft-failed). */
|
|
26
|
+
recommended(roomId: string, version: string, pdu: PDU): Promise<boolean>;
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
export {};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { publicKeyFromBase64, verifyJsonSignature } from "./crypto.js";
|
|
2
|
+
import { redact } from "./events.js";
|
|
3
|
+
import { MatrixError } from "./errors.js";
|
|
4
|
+
import { getState } from "./authrules.js";
|
|
5
|
+
import { domainOf } from "../federation/index.js";
|
|
6
|
+
//#region src/core/policy.ts
|
|
7
|
+
const KEY_ID = "ed25519:policy_server";
|
|
8
|
+
var PolicyServers = class {
|
|
9
|
+
hs;
|
|
10
|
+
constructor(hs) {
|
|
11
|
+
this.hs = hs;
|
|
12
|
+
}
|
|
13
|
+
/** The room's policy server, if the room uses one (valid `m.room.policy` and a joined user on `via`). */
|
|
14
|
+
async policyOf(roomId) {
|
|
15
|
+
const state = await this.hs.rooms.currentState(roomId);
|
|
16
|
+
const content = getState(state, "m.room.policy", "")?.content;
|
|
17
|
+
const via = content?.via;
|
|
18
|
+
const ed25519 = content?.public_keys?.ed25519;
|
|
19
|
+
if (typeof via !== "string" || typeof ed25519 !== "string") return null;
|
|
20
|
+
let key;
|
|
21
|
+
try {
|
|
22
|
+
key = publicKeyFromBase64(ed25519);
|
|
23
|
+
} catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
for (const ev of state.values()) if (ev.type === "m.room.member" && ev.content?.membership === "join" && typeof ev.state_key === "string" && domainOf(ev.state_key) === via) return {
|
|
27
|
+
via,
|
|
28
|
+
key
|
|
29
|
+
};
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
/** The policy state event itself is never sent to the policy server. */
|
|
33
|
+
exempt(pdu) {
|
|
34
|
+
return pdu.type === "m.room.policy" && pdu.state_key === "";
|
|
35
|
+
}
|
|
36
|
+
validSignature(pdu, version, policy) {
|
|
37
|
+
const wire = this.hs.federation.toFederationPdu(pdu, version);
|
|
38
|
+
return verifyJsonSignature(redact(wire, version), policy.via, KEY_ID, policy.key);
|
|
39
|
+
}
|
|
40
|
+
/** Ask the policy server to sign `pdu`; adds the signature to it. Throws the server's error. */
|
|
41
|
+
async requestSignature(pdu, version, policy) {
|
|
42
|
+
const sig = (await this.hs.federation.client.request(policy.via, "POST", "/_matrix/policy/v1/sign", {
|
|
43
|
+
body: this.hs.federation.toFederationPdu(pdu, version),
|
|
44
|
+
timeoutMs: 1e4
|
|
45
|
+
}))?.[policy.via]?.[KEY_ID];
|
|
46
|
+
if (typeof sig !== "string") throw new MatrixError(403, "M_FORBIDDEN", "The policy server did not sign the event");
|
|
47
|
+
pdu.signatures = {
|
|
48
|
+
...pdu.signatures,
|
|
49
|
+
[policy.via]: {
|
|
50
|
+
...pdu.signatures?.[policy.via],
|
|
51
|
+
[KEY_ID]: sig
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Before a local event is sent: get the policy server's signature, or refuse the event with the
|
|
57
|
+
* policy server's error. If the policy server cannot be reached the event is sent unsigned (other
|
|
58
|
+
* servers may then soft-fail it), so an unavailable policy server does not lock up the room.
|
|
59
|
+
*/
|
|
60
|
+
async signLocal(roomId, version, pdu) {
|
|
61
|
+
if (this.exempt(pdu) || !this.hs.federation.enabled) return;
|
|
62
|
+
const policy = await this.policyOf(roomId);
|
|
63
|
+
if (!policy) return;
|
|
64
|
+
if (policy.via === this.hs.serverName) {
|
|
65
|
+
this.hs.logger.warn("Room", roomId, "names this server as its policy server, which it does not implement");
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
await this.requestSignature(pdu, version, policy);
|
|
70
|
+
} catch (err) {
|
|
71
|
+
if (err instanceof MatrixError && err.status < 500) throw err;
|
|
72
|
+
this.hs.logger.warn("Policy server", policy.via, "unreachable; sending", pdu.event_id, "without its signature", err);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (!this.validSignature(pdu, version, policy)) throw new MatrixError(403, "M_FORBIDDEN", "The policy server's signature is invalid");
|
|
76
|
+
}
|
|
77
|
+
/** Whether a remote event is recommended for inclusion (otherwise it is soft-failed). */
|
|
78
|
+
async recommended(roomId, version, pdu) {
|
|
79
|
+
if (this.exempt(pdu)) return true;
|
|
80
|
+
const policy = await this.policyOf(roomId);
|
|
81
|
+
if (!policy) return true;
|
|
82
|
+
if (this.validSignature(pdu, version, policy)) return true;
|
|
83
|
+
if (policy.via === this.hs.serverName) return false;
|
|
84
|
+
const copy = structuredClone(pdu);
|
|
85
|
+
try {
|
|
86
|
+
await this.requestSignature(copy, version, policy);
|
|
87
|
+
} catch {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
if (!this.validSignature(copy, version, policy)) return false;
|
|
91
|
+
pdu.signatures = copy.signatures;
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
//#endregion
|
|
96
|
+
export { PolicyServers };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { canonicalJson } from "./json.js";
|
|
2
|
+
//#region src/core/profile.ts
|
|
3
|
+
const BUILTIN_FIELDS = /* @__PURE__ */ new Set(["displayname", "avatar_url"]);
|
|
4
|
+
const MAX_PROFILE_BYTES = 65536;
|
|
5
|
+
async function fullProfile(hs, userId) {
|
|
6
|
+
const user = await hs.db.findOne("users", [{
|
|
7
|
+
field: "user_id",
|
|
8
|
+
value: userId
|
|
9
|
+
}]);
|
|
10
|
+
if (!user || user.deactivated) return null;
|
|
11
|
+
const out = {};
|
|
12
|
+
for (const row of await hs.db.findMany("profile_fields", { where: [{
|
|
13
|
+
field: "user_id",
|
|
14
|
+
value: userId
|
|
15
|
+
}] })) if (row.value !== null && row.value !== void 0 && !BUILTIN_FIELDS.has(row.field)) out[row.field] = row.value;
|
|
16
|
+
if (user.displayname) out.displayname = user.displayname;
|
|
17
|
+
if (user.avatar_url) out.avatar_url = user.avatar_url;
|
|
18
|
+
return out;
|
|
19
|
+
}
|
|
20
|
+
function profileSize(profile) {
|
|
21
|
+
return Buffer.byteLength(canonicalJson(profile));
|
|
22
|
+
}
|
|
23
|
+
/** Set (or with `null`, clear) one profile field and notify users sharing a room. */
|
|
24
|
+
async function setProfileField(hs, userId, field, value) {
|
|
25
|
+
if (BUILTIN_FIELDS.has(field)) await hs.db.update("users", [{
|
|
26
|
+
field: "user_id",
|
|
27
|
+
value: userId
|
|
28
|
+
}], { [field]: value || null });
|
|
29
|
+
await hs.streams.write((stream) => hs.db.upsert("profile_fields", {
|
|
30
|
+
user_id: userId,
|
|
31
|
+
field,
|
|
32
|
+
value: value ?? null,
|
|
33
|
+
stream
|
|
34
|
+
}));
|
|
35
|
+
if (BUILTIN_FIELDS.has(field)) await hs.rooms.propagateProfile(userId);
|
|
36
|
+
hs.notifier.notify([...await hs.rooms.usersSharingRooms(userId)]);
|
|
37
|
+
}
|
|
38
|
+
//#endregion
|
|
39
|
+
export { BUILTIN_FIELDS, MAX_PROFILE_BYTES, fullProfile, profileSize, setProfileField };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { ClientEvent } from "./events.js";
|
|
2
|
+
import { StateMap } from "./authrules.js";
|
|
3
|
+
import { StoredEvent } from "./rooms.js";
|
|
4
|
+
import "./pushrules.js";
|
|
5
|
+
import { Homeserver } from "../homeserver.js";
|
|
6
|
+
//#region src/core/push.d.ts
|
|
7
|
+
/**
|
|
8
|
+
* Push rule evaluation and push gateway delivery. Every new event is evaluated for
|
|
9
|
+
* every local member; matches are stored for unread counts and `/notifications`.
|
|
10
|
+
*/
|
|
11
|
+
export declare class PushService {
|
|
12
|
+
private hs;
|
|
13
|
+
constructor(hs: Homeserver);
|
|
14
|
+
onEvent(ev: StoredEvent, state: StateMap, version: string): Promise<void>;
|
|
15
|
+
/** Unread notification counts since the user's read receipts. */
|
|
16
|
+
/** Stream positions up to which the user has read the room (unthreaded) and each thread. */
|
|
17
|
+
private readPositions;
|
|
18
|
+
isRead(row: Record<string, any>, read: {
|
|
19
|
+
unthreaded: number;
|
|
20
|
+
threads: Map<string, number>;
|
|
21
|
+
}): boolean;
|
|
22
|
+
unreadCounts(userId: string, roomId: string): Promise<{
|
|
23
|
+
room: {
|
|
24
|
+
notification_count: number;
|
|
25
|
+
highlight_count: number;
|
|
26
|
+
};
|
|
27
|
+
threads: Record<string, {
|
|
28
|
+
notification_count: number;
|
|
29
|
+
highlight_count: number;
|
|
30
|
+
}>;
|
|
31
|
+
}>;
|
|
32
|
+
private deliver;
|
|
33
|
+
/** Rows for `GET /notifications`. */
|
|
34
|
+
list(userId: string, opts: {
|
|
35
|
+
from: number | null;
|
|
36
|
+
limit: number;
|
|
37
|
+
onlyHighlight: boolean;
|
|
38
|
+
}): Promise<{
|
|
39
|
+
actions: any;
|
|
40
|
+
event: ClientEvent;
|
|
41
|
+
room_id: any;
|
|
42
|
+
ts: any;
|
|
43
|
+
read: boolean;
|
|
44
|
+
stream: any;
|
|
45
|
+
}[]>;
|
|
46
|
+
}
|
|
47
|
+
//#endregion
|