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/profile.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { isObject } from "../core/json.js";
|
|
2
|
+
import { M } from "../core/m.js";
|
|
2
3
|
import { MatrixError, errors } from "../core/errors.js";
|
|
3
4
|
import { isValidUserId } from "../core/ids.js";
|
|
4
5
|
import { getState } from "../core/authrules.js";
|
|
5
6
|
import { assertNotSuspended } from "../core/auth.js";
|
|
7
|
+
import { number, string } from "../vendor/zod/v4/classic/schemas.js";
|
|
6
8
|
import { body, fail, lenient } from "../http/input.js";
|
|
9
|
+
import { procedure } from "../http/procedure.js";
|
|
7
10
|
import { BUILTIN_FIELDS, fullProfile, profileSize, setProfileField } from "../core/profile.js";
|
|
8
|
-
import { z } from "zod";
|
|
9
11
|
//#region src/api/profile.ts
|
|
10
12
|
async function remoteProfile(ctx, field) {
|
|
11
13
|
if (!isValidUserId(ctx.params.userId)) throw errors.invalidParam("Invalid user ID");
|
|
@@ -26,17 +28,17 @@ function checkField(field) {
|
|
|
26
28
|
if (!field || Buffer.byteLength(field) > 255) throw new MatrixError(400, "M_KEY_TOO_LARGE", "Profile key too long");
|
|
27
29
|
}
|
|
28
30
|
function registerProfileRoutes(r) {
|
|
29
|
-
r.client("GET", "/profile/:userId", async (ctx) => {
|
|
31
|
+
r.client("GET", "/profile/:userId", procedure({ handler: async (ctx) => {
|
|
30
32
|
if (!ctx.hs.isLocalUser(ctx.params.userId)) return remoteProfile(ctx);
|
|
31
33
|
return localProfile(ctx);
|
|
32
|
-
});
|
|
33
|
-
r.client("GET", "/profile/:userId/:field", async (ctx) => {
|
|
34
|
+
} }));
|
|
35
|
+
r.client("GET", "/profile/:userId/:field", procedure({ handler: async (ctx) => {
|
|
34
36
|
const field = ctx.params.field;
|
|
35
37
|
checkField(field);
|
|
36
38
|
const profile = ctx.hs.isLocalUser(ctx.params.userId) ? await localProfile(ctx) : await remoteProfile(ctx, field);
|
|
37
39
|
if (profile[field] === void 0 || profile[field] === null) throw errors.notFound("Profile field not set");
|
|
38
40
|
return { [field]: profile[field] };
|
|
39
|
-
});
|
|
41
|
+
} }));
|
|
40
42
|
async function owner(ctx) {
|
|
41
43
|
const s = await ctx.auth();
|
|
42
44
|
if (!isValidUserId(ctx.params.userId) || ctx.params.userId !== s.userId) throw errors.forbidden("Cannot change another user's profile");
|
|
@@ -44,7 +46,7 @@ function registerProfileRoutes(r) {
|
|
|
44
46
|
await assertNotSuspended(ctx.hs, s);
|
|
45
47
|
return s;
|
|
46
48
|
}
|
|
47
|
-
r.client("PUT", "/profile/:userId/:field", async (ctx) => {
|
|
49
|
+
r.client("PUT", "/profile/:userId/:field", procedure({ handler: async (ctx) => {
|
|
48
50
|
const s = await owner(ctx);
|
|
49
51
|
const field = ctx.params.field;
|
|
50
52
|
const body = await ctx.json();
|
|
@@ -60,106 +62,115 @@ function registerProfileRoutes(r) {
|
|
|
60
62
|
if (profileSize(profile) > 65536) throw new MatrixError(400, "M_PROFILE_TOO_LARGE", "Profile too large");
|
|
61
63
|
await setProfileField(ctx.hs, s.userId, field, value);
|
|
62
64
|
return {};
|
|
63
|
-
});
|
|
64
|
-
r.client("DELETE", "/profile/:userId/:field", async (ctx) => {
|
|
65
|
+
} }));
|
|
66
|
+
r.client("DELETE", "/profile/:userId/:field", procedure({ handler: async (ctx) => {
|
|
65
67
|
const s = await owner(ctx);
|
|
66
68
|
await setProfileField(ctx.hs, s.userId, ctx.params.field, null);
|
|
67
69
|
return {};
|
|
68
|
-
});
|
|
70
|
+
} }));
|
|
69
71
|
const directoryBody = body({
|
|
70
|
-
search_term:
|
|
71
|
-
limit: lenient(
|
|
72
|
+
search_term: string(fail("M_MISSING_PARAM", "Missing search_term")),
|
|
73
|
+
limit: lenient(number().int())
|
|
72
74
|
});
|
|
73
|
-
r.client("POST", "/user_directory/search",
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
75
|
+
r.client("POST", "/user_directory/search", procedure({
|
|
76
|
+
auth: "user",
|
|
77
|
+
input: directoryBody,
|
|
78
|
+
handler: async (ctx) => {
|
|
79
|
+
const s = ctx.session;
|
|
80
|
+
const body = ctx.input;
|
|
81
|
+
const term = body.search_term.toLowerCase();
|
|
82
|
+
const limit = body.limit !== void 0 ? Math.min(body.limit, 50) : 10;
|
|
83
|
+
const visible = await ctx.hs.rooms.usersSharingRooms(s.userId);
|
|
84
|
+
visible.delete(s.userId);
|
|
85
|
+
for (const room of await ctx.hs.db.findMany("rooms", {})) {
|
|
86
|
+
const state = await ctx.hs.rooms.currentState(room.room_id);
|
|
87
|
+
if (room.is_public || getState(state, M.ROOM.JOIN_RULES)?.content?.join_rule === "public" || getState(state, M.ROOM.HISTORY_VISIBILITY)?.content?.history_visibility === "world_readable") for (const u of await ctx.hs.rooms.joinedMembers(room.room_id)) visible.add(u);
|
|
88
|
+
}
|
|
89
|
+
const candidates = (await ctx.hs.db.findMany("users", { where: [{
|
|
90
|
+
field: "user_id",
|
|
91
|
+
operator: "in",
|
|
92
|
+
value: [...visible].filter((u) => ctx.hs.isLocalUser(u))
|
|
93
|
+
}] })).filter((u) => !u.deactivated).map((u) => ({
|
|
94
|
+
user_id: u.user_id,
|
|
95
|
+
displayname: u.displayname,
|
|
96
|
+
avatar_url: u.avatar_url
|
|
97
|
+
}));
|
|
98
|
+
for (const userId of visible) {
|
|
99
|
+
if (ctx.hs.isLocalUser(userId)) continue;
|
|
100
|
+
const row = (await ctx.hs.db.findMany("memberships", {
|
|
101
|
+
where: [{
|
|
102
|
+
field: "user_id",
|
|
103
|
+
value: userId
|
|
104
|
+
}, {
|
|
105
|
+
field: "membership",
|
|
106
|
+
value: "join"
|
|
107
|
+
}],
|
|
108
|
+
limit: 1
|
|
109
|
+
}))[0];
|
|
110
|
+
const member = row ? await ctx.hs.rooms.getPdu(row.event_id) : null;
|
|
111
|
+
candidates.push({
|
|
112
|
+
user_id: userId,
|
|
113
|
+
displayname: member?.content?.displayname,
|
|
114
|
+
avatar_url: member?.content?.avatar_url
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
const results = candidates.filter((u) => u.user_id.toLowerCase().includes(term) || typeof u.displayname === "string" && u.displayname.toLowerCase().includes(term)).map((u) => ({
|
|
118
|
+
user_id: u.user_id,
|
|
119
|
+
display_name: u.displayname ?? void 0,
|
|
120
|
+
avatar_url: u.avatar_url ?? void 0
|
|
121
|
+
}));
|
|
122
|
+
return {
|
|
123
|
+
results: results.slice(0, limit),
|
|
124
|
+
limited: results.length > limit
|
|
125
|
+
};
|
|
111
126
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
return {
|
|
118
|
-
results: results.slice(0, limit),
|
|
119
|
-
limited: results.length > limit
|
|
120
|
-
};
|
|
121
|
-
});
|
|
122
|
-
r.client("GET", "/presence/:userId/status", async (ctx) => {
|
|
123
|
-
await ctx.auth();
|
|
124
|
-
const row = await ctx.hs.db.findOne("presence", [{
|
|
125
|
-
field: "user_id",
|
|
126
|
-
value: ctx.params.userId
|
|
127
|
-
}]);
|
|
128
|
-
if (!row) {
|
|
129
|
-
if (!await ctx.hs.db.findOne("users", [{
|
|
127
|
+
}));
|
|
128
|
+
r.client("GET", "/presence/:userId/status", procedure({
|
|
129
|
+
auth: "user",
|
|
130
|
+
handler: async (ctx) => {
|
|
131
|
+
const row = await ctx.hs.db.findOne("presence", [{
|
|
130
132
|
field: "user_id",
|
|
131
133
|
value: ctx.params.userId
|
|
132
|
-
}])
|
|
133
|
-
|
|
134
|
+
}]);
|
|
135
|
+
if (!row) {
|
|
136
|
+
if (!await ctx.hs.db.findOne("users", [{
|
|
137
|
+
field: "user_id",
|
|
138
|
+
value: ctx.params.userId
|
|
139
|
+
}])) throw errors.notFound("Unknown user");
|
|
140
|
+
return { presence: "offline" };
|
|
141
|
+
}
|
|
142
|
+
const out = {
|
|
143
|
+
presence: row.presence,
|
|
144
|
+
last_active_ago: Date.now() - row.last_active_ts
|
|
145
|
+
};
|
|
146
|
+
if (row.status_msg) out.status_msg = row.status_msg;
|
|
147
|
+
if (row.presence === "online") out.currently_active = true;
|
|
148
|
+
return out;
|
|
134
149
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
await ctx.hs.federation.onPresence(s.userId, body.presence, typeof body.status_msg === "string" ? body.status_msg : null).catch(() => {});
|
|
160
|
-
ctx.hs.notifier.notify(await ctx.hs.rooms.usersSharingRooms(s.userId));
|
|
161
|
-
return {};
|
|
162
|
-
});
|
|
150
|
+
}));
|
|
151
|
+
r.client("PUT", "/presence/:userId/status", procedure({
|
|
152
|
+
auth: "user",
|
|
153
|
+
handler: async (ctx) => {
|
|
154
|
+
const s = ctx.session;
|
|
155
|
+
if (ctx.params.userId !== s.userId) throw errors.forbidden("Cannot set another user's presence");
|
|
156
|
+
const body = await ctx.json();
|
|
157
|
+
if (![
|
|
158
|
+
"online",
|
|
159
|
+
"offline",
|
|
160
|
+
"unavailable"
|
|
161
|
+
].includes(body.presence)) throw errors.invalidParam("Invalid presence");
|
|
162
|
+
await ctx.hs.streams.write((stream) => ctx.hs.db.upsert("presence", {
|
|
163
|
+
user_id: s.userId,
|
|
164
|
+
presence: body.presence,
|
|
165
|
+
status_msg: typeof body.status_msg === "string" ? body.status_msg : null,
|
|
166
|
+
last_active_ts: Date.now(),
|
|
167
|
+
stream
|
|
168
|
+
}));
|
|
169
|
+
await ctx.hs.federation.onPresence(s.userId, body.presence, typeof body.status_msg === "string" ? body.status_msg : null).catch(() => {});
|
|
170
|
+
ctx.hs.notifier.notify(await ctx.hs.rooms.usersSharingRooms(s.userId));
|
|
171
|
+
return {};
|
|
172
|
+
}
|
|
173
|
+
}));
|
|
163
174
|
}
|
|
164
175
|
//#endregion
|
|
165
176
|
export { registerProfileRoutes };
|
package/dist/api/push.js
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
|
+
import { M } from "../core/m.js";
|
|
1
2
|
import { errors } from "../core/errors.js";
|
|
2
3
|
import { KINDS, loadRules, saveRules, withRulesLock } from "../core/pushrules.js";
|
|
4
|
+
import { looseObject, string, unknown } from "../vendor/zod/v4/classic/schemas.js";
|
|
3
5
|
import { body, fail, lenient } from "../http/input.js";
|
|
4
|
-
import {
|
|
6
|
+
import { procedure } from "../http/procedure.js";
|
|
5
7
|
//#region src/api/push.ts
|
|
6
|
-
const pusherKey =
|
|
8
|
+
const pusherKey = string(fail("M_MISSING_PARAM", "Missing app_id or pushkey"));
|
|
7
9
|
const pusherBody = body({
|
|
8
10
|
app_id: pusherKey,
|
|
9
11
|
pushkey: pusherKey,
|
|
10
|
-
kind: lenient(
|
|
11
|
-
app_display_name: lenient(
|
|
12
|
-
device_display_name: lenient(
|
|
13
|
-
lang: lenient(
|
|
14
|
-
data: lenient(
|
|
15
|
-
append:
|
|
12
|
+
kind: lenient(string().nullable()),
|
|
13
|
+
app_display_name: lenient(string()),
|
|
14
|
+
device_display_name: lenient(string()),
|
|
15
|
+
lang: lenient(string()),
|
|
16
|
+
data: lenient(looseObject({ url: lenient(string()) })),
|
|
17
|
+
append: unknown().optional()
|
|
16
18
|
});
|
|
17
19
|
function findRule(rules, kind, ruleId) {
|
|
18
20
|
if (!KINDS.includes(kind)) throw errors.invalidParam("Unknown rule kind");
|
|
@@ -29,27 +31,30 @@ async function ruleCtx(ctx) {
|
|
|
29
31
|
};
|
|
30
32
|
}
|
|
31
33
|
function registerPushRoutes(r) {
|
|
32
|
-
r.client("GET", "/pushrules",
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
r.client("GET", "/pushrules", procedure({
|
|
35
|
+
auth: "user",
|
|
36
|
+
handler: async (ctx) => {
|
|
37
|
+
const s = ctx.session;
|
|
38
|
+
return { global: await loadRules(ctx.hs, s.userId) };
|
|
39
|
+
}
|
|
40
|
+
}));
|
|
41
|
+
r.client("GET", "/pushrules/:scope", procedure({ handler: async (ctx) => {
|
|
37
42
|
const { rules } = await ruleCtx(ctx);
|
|
38
43
|
return rules;
|
|
39
|
-
});
|
|
40
|
-
r.client("GET", "/pushrules/:scope/:kind/:ruleId", async (ctx) => {
|
|
44
|
+
} }));
|
|
45
|
+
r.client("GET", "/pushrules/:scope/:kind/:ruleId", procedure({ handler: async (ctx) => {
|
|
41
46
|
const { rules } = await ruleCtx(ctx);
|
|
42
47
|
return findRule(rules, ctx.params.kind, ctx.params.ruleId);
|
|
43
|
-
});
|
|
44
|
-
r.client("DELETE", "/pushrules/:scope/:kind/:ruleId", async (ctx) => withRulesLock(ctx.hs, (await ctx.auth()).userId, async () => {
|
|
48
|
+
} }));
|
|
49
|
+
r.client("DELETE", "/pushrules/:scope/:kind/:ruleId", procedure({ handler: async (ctx) => withRulesLock(ctx.hs, (await ctx.auth()).userId, async () => {
|
|
45
50
|
const { s, rules } = await ruleCtx(ctx);
|
|
46
51
|
const rule = findRule(rules, ctx.params.kind, ctx.params.ruleId);
|
|
47
52
|
if (rule.default) throw errors.forbidden("Cannot delete a default rule");
|
|
48
53
|
rules[ctx.params.kind] = rules[ctx.params.kind].filter((x) => x !== rule);
|
|
49
54
|
await saveRules(ctx.hs, s.userId, rules);
|
|
50
55
|
return {};
|
|
51
|
-
}));
|
|
52
|
-
r.client("PUT", "/pushrules/:scope/:kind/:ruleId", async (ctx) => withRulesLock(ctx.hs, (await ctx.auth()).userId, async () => {
|
|
56
|
+
}) }));
|
|
57
|
+
r.client("PUT", "/pushrules/:scope/:kind/:ruleId", procedure({ handler: async (ctx) => withRulesLock(ctx.hs, (await ctx.auth()).userId, async () => {
|
|
53
58
|
const { s, rules } = await ruleCtx(ctx);
|
|
54
59
|
const { kind, ruleId } = ctx.params;
|
|
55
60
|
if (!KINDS.includes(kind)) throw errors.invalidParam("Unknown rule kind");
|
|
@@ -70,7 +75,7 @@ function registerPushRoutes(r) {
|
|
|
70
75
|
const list = (rules[kind] ??= []).filter((x) => x.rule_id !== ruleId);
|
|
71
76
|
const before = ctx.query.get("before");
|
|
72
77
|
const after = ctx.query.get("after");
|
|
73
|
-
let index = list.findIndex((x) => x.default && x.rule_id !==
|
|
78
|
+
let index = list.findIndex((x) => x.default && x.rule_id !== M.RULE.MASTER);
|
|
74
79
|
if (index === -1) index = list.length;
|
|
75
80
|
if (before) {
|
|
76
81
|
index = list.findIndex((x) => x.rule_id === before);
|
|
@@ -84,13 +89,13 @@ function registerPushRoutes(r) {
|
|
|
84
89
|
rules[kind] = list;
|
|
85
90
|
await saveRules(ctx.hs, s.userId, rules);
|
|
86
91
|
return {};
|
|
87
|
-
}));
|
|
92
|
+
}) }));
|
|
88
93
|
for (const attr of ["enabled", "actions"]) {
|
|
89
|
-
r.client("GET", `/pushrules/:scope/:kind/:ruleId/${attr}`, async (ctx) => {
|
|
94
|
+
r.client("GET", `/pushrules/:scope/:kind/:ruleId/${attr}`, procedure({ handler: async (ctx) => {
|
|
90
95
|
const { rules } = await ruleCtx(ctx);
|
|
91
96
|
return { [attr]: findRule(rules, ctx.params.kind, ctx.params.ruleId)[attr] };
|
|
92
|
-
});
|
|
93
|
-
r.client("PUT", `/pushrules/:scope/:kind/:ruleId/${attr}`, async (ctx) => withRulesLock(ctx.hs, (await ctx.auth()).userId, async () => {
|
|
97
|
+
} }));
|
|
98
|
+
r.client("PUT", `/pushrules/:scope/:kind/:ruleId/${attr}`, procedure({ handler: async (ctx) => withRulesLock(ctx.hs, (await ctx.auth()).userId, async () => {
|
|
94
99
|
const { s, rules } = await ruleCtx(ctx);
|
|
95
100
|
const rule = findRule(rules, ctx.params.kind, ctx.params.ruleId);
|
|
96
101
|
const body = await ctx.json();
|
|
@@ -99,89 +104,99 @@ function registerPushRoutes(r) {
|
|
|
99
104
|
rule[attr] = body[attr];
|
|
100
105
|
await saveRules(ctx.hs, s.userId, rules);
|
|
101
106
|
return {};
|
|
102
|
-
}));
|
|
107
|
+
}) }));
|
|
103
108
|
}
|
|
104
|
-
r.client("GET", "/pushers",
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
109
|
+
r.client("GET", "/pushers", procedure({
|
|
110
|
+
auth: "user",
|
|
111
|
+
handler: async (ctx) => {
|
|
112
|
+
const s = ctx.session;
|
|
113
|
+
return { pushers: (await ctx.hs.db.findMany("pushers", { where: [{
|
|
114
|
+
field: "user_id",
|
|
115
|
+
value: s.userId
|
|
116
|
+
}] })).map((r) => r.pusher) };
|
|
117
|
+
}
|
|
118
|
+
}));
|
|
119
|
+
r.client("POST", "/pushers/set", procedure({
|
|
120
|
+
auth: "user",
|
|
121
|
+
input: pusherBody,
|
|
122
|
+
handler: async (ctx) => {
|
|
123
|
+
const s = ctx.session;
|
|
124
|
+
const body = ctx.input;
|
|
125
|
+
if (body.kind === null) {
|
|
126
|
+
await ctx.hs.db.delete("pushers", [
|
|
127
|
+
{
|
|
128
|
+
field: "user_id",
|
|
129
|
+
value: s.userId
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
field: "app_id",
|
|
133
|
+
value: body.app_id
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
field: "pushkey",
|
|
137
|
+
value: body.pushkey
|
|
138
|
+
}
|
|
139
|
+
]);
|
|
140
|
+
return {};
|
|
141
|
+
}
|
|
142
|
+
for (const k of [
|
|
143
|
+
"kind",
|
|
144
|
+
"app_display_name",
|
|
145
|
+
"device_display_name",
|
|
146
|
+
"lang"
|
|
147
|
+
]) if (body[k] === void 0) throw errors.missingParam(`Missing ${k}`);
|
|
148
|
+
if (!body.data) throw errors.missingParam("Missing data");
|
|
149
|
+
if (body.kind === "http" && body.data.url === void 0) throw errors.invalidParam("data.url is required for http pushers");
|
|
150
|
+
if (!body.append) {
|
|
151
|
+
const others = await ctx.hs.db.findMany("pushers", { where: [{
|
|
121
152
|
field: "app_id",
|
|
122
153
|
value: body.app_id
|
|
123
|
-
},
|
|
124
|
-
{
|
|
154
|
+
}, {
|
|
125
155
|
field: "pushkey",
|
|
126
156
|
value: body.pushkey
|
|
127
|
-
}
|
|
128
|
-
|
|
157
|
+
}] });
|
|
158
|
+
for (const o of others) await ctx.hs.db.delete("pushers", [
|
|
159
|
+
{
|
|
160
|
+
field: "user_id",
|
|
161
|
+
value: o.user_id
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
field: "app_id",
|
|
165
|
+
value: o.app_id
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
field: "pushkey",
|
|
169
|
+
value: o.pushkey
|
|
170
|
+
}
|
|
171
|
+
]);
|
|
172
|
+
}
|
|
173
|
+
const { append, ...pusher } = body;
|
|
174
|
+
await ctx.hs.db.upsert("pushers", {
|
|
175
|
+
user_id: s.userId,
|
|
176
|
+
app_id: body.app_id,
|
|
177
|
+
pushkey: body.pushkey,
|
|
178
|
+
device_id: s.deviceId,
|
|
179
|
+
pusher
|
|
180
|
+
});
|
|
129
181
|
return {};
|
|
130
182
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
}] });
|
|
147
|
-
for (const o of others) await ctx.hs.db.delete("pushers", [
|
|
148
|
-
{
|
|
149
|
-
field: "user_id",
|
|
150
|
-
value: o.user_id
|
|
151
|
-
},
|
|
152
|
-
{
|
|
153
|
-
field: "app_id",
|
|
154
|
-
value: o.app_id
|
|
155
|
-
},
|
|
156
|
-
{
|
|
157
|
-
field: "pushkey",
|
|
158
|
-
value: o.pushkey
|
|
159
|
-
}
|
|
160
|
-
]);
|
|
183
|
+
}));
|
|
184
|
+
r.client("GET", "/notifications", procedure({
|
|
185
|
+
auth: "user",
|
|
186
|
+
handler: async (ctx) => {
|
|
187
|
+
const s = ctx.session;
|
|
188
|
+
const from = ctx.query.get("from");
|
|
189
|
+
const limit = Math.min(Math.max(Number.parseInt(ctx.query.get("limit") ?? "20", 10) || 20, 1), 100);
|
|
190
|
+
const rows = await ctx.hs.push.list(s.userId, {
|
|
191
|
+
from: from ? Number.parseInt(from, 10) : null,
|
|
192
|
+
limit,
|
|
193
|
+
onlyHighlight: ctx.query.get("only") === "highlight"
|
|
194
|
+
});
|
|
195
|
+
const out = { notifications: rows.map(({ stream, ...n }) => n) };
|
|
196
|
+
if (rows.length === limit) out.next_token = String(rows[rows.length - 1].stream);
|
|
197
|
+
return out;
|
|
161
198
|
}
|
|
162
|
-
|
|
163
|
-
await ctx.hs.db.upsert("pushers", {
|
|
164
|
-
user_id: s.userId,
|
|
165
|
-
app_id: body.app_id,
|
|
166
|
-
pushkey: body.pushkey,
|
|
167
|
-
device_id: s.deviceId,
|
|
168
|
-
pusher
|
|
169
|
-
});
|
|
170
|
-
return {};
|
|
171
|
-
});
|
|
172
|
-
r.client("GET", "/notifications", async (ctx) => {
|
|
173
|
-
const s = await ctx.auth();
|
|
174
|
-
const from = ctx.query.get("from");
|
|
175
|
-
const limit = Math.min(Math.max(Number.parseInt(ctx.query.get("limit") ?? "20", 10) || 20, 1), 100);
|
|
176
|
-
const rows = await ctx.hs.push.list(s.userId, {
|
|
177
|
-
from: from ? Number.parseInt(from, 10) : null,
|
|
178
|
-
limit,
|
|
179
|
-
onlyHighlight: ctx.query.get("only") === "highlight"
|
|
180
|
-
});
|
|
181
|
-
const out = { notifications: rows.map(({ stream, ...n }) => n) };
|
|
182
|
-
if (rows.length === limit) out.next_token = String(rows[rows.length - 1].stream);
|
|
183
|
-
return out;
|
|
184
|
-
});
|
|
199
|
+
}));
|
|
185
200
|
}
|
|
186
201
|
//#endregion
|
|
187
202
|
export { registerPushRoutes };
|