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.
Files changed (99) hide show
  1. package/README.md +89 -11
  2. package/dist/adapters/cluster.d.ts +1 -26
  3. package/dist/adapters/cluster.js +50 -103
  4. package/dist/api/account.js +407 -354
  5. package/dist/api/admin.js +82 -61
  6. package/dist/api/appservice.js +37 -25
  7. package/dist/api/backup.js +73 -62
  8. package/dist/api/federation.js +172 -138
  9. package/dist/api/filters.js +4 -3
  10. package/dist/api/legacy.js +91 -78
  11. package/dist/api/media.js +71 -55
  12. package/dist/api/misc.js +46 -37
  13. package/dist/api/preview.js +37 -73
  14. package/dist/api/profile.js +108 -97
  15. package/dist/api/push.js +114 -99
  16. package/dist/api/relationships.js +64 -58
  17. package/dist/api/rooms.js +703 -580
  18. package/dist/api/rtc.d.ts +0 -1
  19. package/dist/api/rtc.js +48 -34
  20. package/dist/api/search.js +133 -127
  21. package/dist/api/slidingsync.js +41 -31
  22. package/dist/api/spaces.js +82 -77
  23. package/dist/api/sync.js +325 -135
  24. package/dist/api/threadsubs.js +72 -61
  25. package/dist/api/threepid.js +99 -66
  26. package/dist/api/uia.js +17 -16
  27. package/dist/api/user.js +221 -178
  28. package/dist/core/admin.d.ts +0 -1
  29. package/dist/core/admin.js +6 -5
  30. package/dist/core/adminroom.js +9 -8
  31. package/dist/core/appservices.d.ts +2 -1
  32. package/dist/core/appservices.js +5 -4
  33. package/dist/core/audit.d.ts +0 -1
  34. package/dist/core/authrules.js +26 -25
  35. package/dist/core/cluster.d.ts +12 -5
  36. package/dist/core/cluster.js +46 -29
  37. package/dist/core/delayed.js +2 -1
  38. package/dist/core/events.js +11 -10
  39. package/dist/core/html.js +13 -0
  40. package/dist/core/identity.js +1 -1
  41. package/dist/core/invitefilter.js +2 -1
  42. package/dist/core/json.js +10 -1
  43. package/dist/core/keys.d.ts +10 -1
  44. package/dist/core/keys.js +62 -42
  45. package/dist/core/m.d.ts +209 -0
  46. package/dist/core/m.js +182 -0
  47. package/dist/core/outbound.d.ts +16 -0
  48. package/dist/core/outbound.js +172 -0
  49. package/dist/core/partialstate.js +2 -1
  50. package/dist/core/policy.js +4 -3
  51. package/dist/core/push.d.ts +0 -1
  52. package/dist/core/push.js +41 -28
  53. package/dist/core/pushrules.js +36 -35
  54. package/dist/core/ratelimit.d.ts +1 -1
  55. package/dist/core/rooms.d.ts +10 -1
  56. package/dist/core/rooms.js +101 -74
  57. package/dist/core/semaphore.js +23 -0
  58. package/dist/core/sso.d.ts +15 -1
  59. package/dist/core/sso.js +38 -2
  60. package/dist/core/stateres.js +7 -6
  61. package/dist/core/threepid.js +1 -9
  62. package/dist/federation/client.js +9 -1
  63. package/dist/federation/index.d.ts +2 -2
  64. package/dist/federation/index.js +43 -35
  65. package/dist/homeserver.d.ts +23 -4
  66. package/dist/homeserver.js +12 -3
  67. package/dist/http/handler.js +2 -23
  68. package/dist/http/input.js +2 -2
  69. package/dist/http/procedure.js +25 -0
  70. package/dist/http/router.d.ts +9 -26
  71. package/dist/http/router.js +10 -30
  72. package/dist/index.d.ts +4 -3
  73. package/dist/index.js +3 -2
  74. package/dist/storage/batch.js +11 -0
  75. package/dist/storage/memory.js +1 -0
  76. package/dist/storage/schema.js +1 -1
  77. package/dist/storage/sql.js +20 -1
  78. package/dist/storage/types.d.ts +16 -0
  79. package/dist/vendor/zod/LICENSE +21 -0
  80. package/dist/vendor/zod/v4/classic/errors.js +54 -0
  81. package/dist/vendor/zod/v4/classic/parse.js +17 -0
  82. package/dist/vendor/zod/v4/classic/schemas.js +865 -0
  83. package/dist/vendor/zod/v4/core/api.js +512 -0
  84. package/dist/vendor/zod/v4/core/checks.js +300 -0
  85. package/dist/vendor/zod/v4/core/core.js +109 -0
  86. package/dist/vendor/zod/v4/core/doc.js +35 -0
  87. package/dist/vendor/zod/v4/core/errors.js +118 -0
  88. package/dist/vendor/zod/v4/core/json-schema-processors.js +428 -0
  89. package/dist/vendor/zod/v4/core/memoizer.js +255 -0
  90. package/dist/vendor/zod/v4/core/parse.js +206 -0
  91. package/dist/vendor/zod/v4/core/regexes.js +73 -0
  92. package/dist/vendor/zod/v4/core/registries.js +48 -0
  93. package/dist/vendor/zod/v4/core/schemas.js +1378 -0
  94. package/dist/vendor/zod/v4/core/to-json-schema.js +448 -0
  95. package/dist/vendor/zod/v4/core/util.js +537 -0
  96. package/dist/vendor/zod/v4/core/versions.js +8 -0
  97. package/dist/vendor/zod/v4/locales/en.js +110 -0
  98. package/package.json +8 -6
  99. package/dist/core/pushrules.d.ts +0 -1
@@ -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: z.string(fail("M_MISSING_PARAM", "Missing search_term")),
71
- limit: lenient(z.number().int())
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", { input: directoryBody }, async (ctx) => {
74
- const s = await ctx.auth();
75
- const body = ctx.input;
76
- const term = body.search_term.toLowerCase();
77
- const limit = body.limit !== void 0 ? Math.min(body.limit, 50) : 10;
78
- const visible = await ctx.hs.rooms.usersSharingRooms(s.userId);
79
- visible.delete(s.userId);
80
- for (const room of await ctx.hs.db.findMany("rooms", {})) {
81
- const state = await ctx.hs.rooms.currentState(room.room_id);
82
- 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);
83
- }
84
- const candidates = (await ctx.hs.db.findMany("users", { where: [{
85
- field: "user_id",
86
- operator: "in",
87
- value: [...visible].filter((u) => ctx.hs.isLocalUser(u))
88
- }] })).filter((u) => !u.deactivated).map((u) => ({
89
- user_id: u.user_id,
90
- displayname: u.displayname,
91
- avatar_url: u.avatar_url
92
- }));
93
- for (const userId of visible) {
94
- if (ctx.hs.isLocalUser(userId)) continue;
95
- const row = (await ctx.hs.db.findMany("memberships", {
96
- where: [{
97
- field: "user_id",
98
- value: userId
99
- }, {
100
- field: "membership",
101
- value: "join"
102
- }],
103
- limit: 1
104
- }))[0];
105
- const member = row ? await ctx.hs.rooms.getPdu(row.event_id) : null;
106
- candidates.push({
107
- user_id: userId,
108
- displayname: member?.content?.displayname,
109
- avatar_url: member?.content?.avatar_url
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
- const results = candidates.filter((u) => u.user_id.toLowerCase().includes(term) || typeof u.displayname === "string" && u.displayname.toLowerCase().includes(term)).map((u) => ({
113
- user_id: u.user_id,
114
- display_name: u.displayname ?? void 0,
115
- avatar_url: u.avatar_url ?? void 0
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
- }])) throw errors.notFound("Unknown user");
133
- return { presence: "offline" };
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
- const out = {
136
- presence: row.presence,
137
- last_active_ago: Date.now() - row.last_active_ts
138
- };
139
- if (row.status_msg) out.status_msg = row.status_msg;
140
- if (row.presence === "online") out.currently_active = true;
141
- return out;
142
- });
143
- r.client("PUT", "/presence/:userId/status", async (ctx) => {
144
- const s = await ctx.auth();
145
- if (ctx.params.userId !== s.userId) throw errors.forbidden("Cannot set another user's presence");
146
- const body = await ctx.json();
147
- if (![
148
- "online",
149
- "offline",
150
- "unavailable"
151
- ].includes(body.presence)) throw errors.invalidParam("Invalid presence");
152
- await ctx.hs.streams.write((stream) => ctx.hs.db.upsert("presence", {
153
- user_id: s.userId,
154
- presence: body.presence,
155
- status_msg: typeof body.status_msg === "string" ? body.status_msg : null,
156
- last_active_ts: Date.now(),
157
- stream
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 { z } from "zod";
6
+ import { procedure } from "../http/procedure.js";
5
7
  //#region src/api/push.ts
6
- const pusherKey = z.string(fail("M_MISSING_PARAM", "Missing app_id or pushkey"));
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(z.string().nullable()),
11
- app_display_name: lenient(z.string()),
12
- device_display_name: lenient(z.string()),
13
- lang: lenient(z.string()),
14
- data: lenient(z.looseObject({ url: lenient(z.string()) })),
15
- append: z.unknown().optional()
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", async (ctx) => {
33
- const s = await ctx.auth();
34
- return { global: await loadRules(ctx.hs, s.userId) };
35
- });
36
- r.client("GET", "/pushrules/:scope", async (ctx) => {
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 !== ".m.rule.master");
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", async (ctx) => {
105
- const s = await ctx.auth();
106
- return { pushers: (await ctx.hs.db.findMany("pushers", { where: [{
107
- field: "user_id",
108
- value: s.userId
109
- }] })).map((r) => r.pusher) };
110
- });
111
- r.client("POST", "/pushers/set", { input: pusherBody }, async (ctx) => {
112
- const s = await ctx.auth();
113
- const body = ctx.input;
114
- if (body.kind === null) {
115
- await ctx.hs.db.delete("pushers", [
116
- {
117
- field: "user_id",
118
- value: s.userId
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
- for (const k of [
132
- "kind",
133
- "app_display_name",
134
- "device_display_name",
135
- "lang"
136
- ]) if (body[k] === void 0) throw errors.missingParam(`Missing ${k}`);
137
- if (!body.data) throw errors.missingParam("Missing data");
138
- if (body.kind === "http" && body.data.url === void 0) throw errors.invalidParam("data.url is required for http pushers");
139
- if (!body.append) {
140
- const others = await ctx.hs.db.findMany("pushers", { where: [{
141
- field: "app_id",
142
- value: body.app_id
143
- }, {
144
- field: "pushkey",
145
- value: body.pushkey
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
- const { append, ...pusher } = body;
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 };