clay-server 2.27.0-beta.9 → 2.27.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/README.md +10 -0
- package/lib/daemon-projects.js +164 -0
- package/lib/daemon.js +13 -126
- package/lib/mates-identity.js +132 -0
- package/lib/mates-knowledge.js +113 -0
- package/lib/mates-prompts.js +398 -0
- package/lib/mates.js +40 -599
- package/lib/project-connection.js +2 -0
- package/lib/project-http.js +4 -2
- package/lib/project-loop.js +110 -48
- package/lib/project-mate-interaction.js +4 -0
- package/lib/project-notifications.js +210 -0
- package/lib/project-sessions.js +5 -2
- package/lib/project-user-message.js +2 -1
- package/lib/project.js +26 -2
- package/lib/public/app.js +1193 -8517
- package/lib/public/css/command-palette.css +14 -0
- package/lib/public/css/loop.css +301 -0
- package/lib/public/css/notifications-center.css +190 -0
- package/lib/public/css/rewind.css +6 -0
- package/lib/public/index.html +89 -35
- package/lib/public/modules/app-connection.js +160 -0
- package/lib/public/modules/app-cursors.js +473 -0
- package/lib/public/modules/app-debate-ui.js +389 -0
- package/lib/public/modules/app-dm.js +627 -0
- package/lib/public/modules/app-favicon.js +212 -0
- package/lib/public/modules/app-header.js +229 -0
- package/lib/public/modules/app-home-hub.js +600 -0
- package/lib/public/modules/app-loop-ui.js +589 -0
- package/lib/public/modules/app-loop-wizard.js +439 -0
- package/lib/public/modules/app-messages.js +1560 -0
- package/lib/public/modules/app-misc.js +299 -0
- package/lib/public/modules/app-notifications.js +372 -0
- package/lib/public/modules/app-panels.js +888 -0
- package/lib/public/modules/app-projects.js +798 -0
- package/lib/public/modules/app-rate-limit.js +451 -0
- package/lib/public/modules/app-rendering.js +597 -0
- package/lib/public/modules/app-skills-install.js +234 -0
- package/lib/public/modules/command-palette.js +27 -4
- package/lib/public/modules/input.js +31 -20
- package/lib/public/modules/scheduler-config.js +1532 -0
- package/lib/public/modules/scheduler-history.js +79 -0
- package/lib/public/modules/scheduler.js +33 -1554
- package/lib/public/modules/session-search.js +13 -1
- package/lib/public/modules/sidebar-mates.js +812 -0
- package/lib/public/modules/sidebar-mobile.js +1269 -0
- package/lib/public/modules/sidebar-projects.js +1449 -0
- package/lib/public/modules/sidebar-sessions.js +986 -0
- package/lib/public/modules/sidebar.js +232 -4591
- package/lib/public/modules/store.js +27 -0
- package/lib/public/modules/ws-ref.js +7 -0
- package/lib/public/style.css +1 -0
- package/lib/sdk-bridge.js +96 -717
- package/lib/sdk-message-processor.js +587 -0
- package/lib/sdk-message-queue.js +42 -0
- package/lib/sdk-skill-discovery.js +131 -0
- package/lib/server-admin.js +712 -0
- package/lib/server-auth.js +737 -0
- package/lib/server-dm.js +221 -0
- package/lib/server-mates.js +281 -0
- package/lib/server-palette.js +110 -0
- package/lib/server-settings.js +479 -0
- package/lib/server-skills.js +280 -0
- package/lib/server.js +246 -2755
- package/lib/sessions.js +11 -4
- package/lib/users-auth.js +146 -0
- package/lib/users-permissions.js +118 -0
- package/lib/users-preferences.js +210 -0
- package/lib/users.js +48 -398
- package/lib/ws-schema.js +498 -0
- package/package.json +1 -1
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
var fs = require("fs");
|
|
2
|
+
var path = require("path");
|
|
3
|
+
|
|
4
|
+
function attachSettings(ctx) {
|
|
5
|
+
var users = ctx.users;
|
|
6
|
+
var mates = ctx.mates;
|
|
7
|
+
var getMultiUserFromReq = ctx.getMultiUserFromReq;
|
|
8
|
+
var projects = ctx.projects;
|
|
9
|
+
var opts = ctx.opts;
|
|
10
|
+
var CONFIG_DIR = ctx.CONFIG_DIR;
|
|
11
|
+
|
|
12
|
+
var profilePath = path.join(CONFIG_DIR, "profile.json");
|
|
13
|
+
|
|
14
|
+
function handleRequest(req, res, fullUrl) {
|
|
15
|
+
// GET /api/profile
|
|
16
|
+
if (req.method === "GET" && fullUrl === "/api/profile") {
|
|
17
|
+
if (users.isMultiUser()) {
|
|
18
|
+
var mu = getMultiUserFromReq(req);
|
|
19
|
+
if (!mu) {
|
|
20
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
21
|
+
res.end('{"error":"unauthorized"}');
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
var profile = mu.profile || { name: "", lang: "en-US", avatarColor: "#7c3aed", avatarStyle: "thumbs", avatarSeed: "", avatarCustom: "" };
|
|
25
|
+
profile.username = mu.username;
|
|
26
|
+
profile.userId = mu.id;
|
|
27
|
+
profile.role = mu.role;
|
|
28
|
+
profile.autoContinueOnRateLimit = !!mu.autoContinueOnRateLimit;
|
|
29
|
+
profile.chatLayout = mu.chatLayout || "channel";
|
|
30
|
+
profile.mateOnboardingShown = !!mu.mateOnboardingShown;
|
|
31
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
32
|
+
res.end(JSON.stringify(profile));
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
var profile = { name: "", lang: "en-US", avatarColor: "#7c3aed", avatarStyle: "thumbs", avatarSeed: "", avatarCustom: "" };
|
|
36
|
+
try {
|
|
37
|
+
var raw = fs.readFileSync(profilePath, "utf8");
|
|
38
|
+
var saved = JSON.parse(raw);
|
|
39
|
+
if (saved.name !== undefined) profile.name = saved.name;
|
|
40
|
+
if (saved.lang) profile.lang = saved.lang;
|
|
41
|
+
if (saved.avatarColor) profile.avatarColor = saved.avatarColor;
|
|
42
|
+
if (saved.avatarStyle) profile.avatarStyle = saved.avatarStyle;
|
|
43
|
+
if (saved.avatarSeed) profile.avatarSeed = saved.avatarSeed;
|
|
44
|
+
if (saved.avatarCustom) profile.avatarCustom = saved.avatarCustom;
|
|
45
|
+
} catch (e) { /* file doesn't exist yet */ }
|
|
46
|
+
// Single-user settings from daemon config
|
|
47
|
+
if (typeof opts.onGetDaemonConfig === "function") {
|
|
48
|
+
var dc = opts.onGetDaemonConfig();
|
|
49
|
+
profile.autoContinueOnRateLimit = !!dc.autoContinueOnRateLimit;
|
|
50
|
+
profile.chatLayout = dc.chatLayout || "channel";
|
|
51
|
+
profile.mateOnboardingShown = !!dc.mateOnboardingShown;
|
|
52
|
+
}
|
|
53
|
+
// Check if custom avatar file exists
|
|
54
|
+
try {
|
|
55
|
+
var avatarFiles = fs.readdirSync(path.join(CONFIG_DIR, "avatars"));
|
|
56
|
+
for (var afi = 0; afi < avatarFiles.length; afi++) {
|
|
57
|
+
if (avatarFiles[afi].startsWith("default.")) {
|
|
58
|
+
profile.avatarCustom = "/api/avatar/default?v=" + fs.statSync(path.join(CONFIG_DIR, "avatars", avatarFiles[afi])).mtimeMs;
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
} catch (e) {}
|
|
63
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
64
|
+
res.end(JSON.stringify(profile));
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// PUT /api/profile
|
|
69
|
+
if (req.method === "PUT" && fullUrl === "/api/profile") {
|
|
70
|
+
var body = "";
|
|
71
|
+
req.on("data", function (chunk) { body += chunk; });
|
|
72
|
+
req.on("end", function () {
|
|
73
|
+
try {
|
|
74
|
+
var data = JSON.parse(body);
|
|
75
|
+
var profile = {};
|
|
76
|
+
if (typeof data.name === "string") profile.name = data.name.substring(0, 50);
|
|
77
|
+
if (typeof data.lang === "string") profile.lang = data.lang.substring(0, 10);
|
|
78
|
+
if (typeof data.avatarColor === "string" && /^#[0-9a-fA-F]{6}$/.test(data.avatarColor)) {
|
|
79
|
+
profile.avatarColor = data.avatarColor;
|
|
80
|
+
}
|
|
81
|
+
if (typeof data.avatarStyle === "string") profile.avatarStyle = data.avatarStyle.substring(0, 30);
|
|
82
|
+
if (typeof data.avatarSeed === "string") profile.avatarSeed = data.avatarSeed.substring(0, 30);
|
|
83
|
+
if (typeof data.avatarCustom === "string") profile.avatarCustom = data.avatarCustom;
|
|
84
|
+
if (data.avatarCustom === null || data.avatarCustom === "") profile.avatarCustom = undefined;
|
|
85
|
+
if (users.isMultiUser()) {
|
|
86
|
+
var mu = getMultiUserFromReq(req);
|
|
87
|
+
if (!mu) {
|
|
88
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
89
|
+
res.end('{"error":"unauthorized"}');
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
users.updateUserProfile(mu.id, profile);
|
|
93
|
+
// Broadcast updated avatar/presence to all projects
|
|
94
|
+
projects.forEach(function (pCtx) {
|
|
95
|
+
pCtx.refreshUserProfile(mu.id);
|
|
96
|
+
});
|
|
97
|
+
} else {
|
|
98
|
+
fs.writeFileSync(profilePath, JSON.stringify(profile, null, 2), "utf8");
|
|
99
|
+
if (process.platform !== "win32") {
|
|
100
|
+
try { fs.chmodSync(profilePath, 0o600); } catch (chmodErr) {}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
104
|
+
res.end(JSON.stringify(profile));
|
|
105
|
+
} catch (e) {
|
|
106
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
107
|
+
res.end(JSON.stringify({ error: "Invalid request" }));
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Upload custom avatar image
|
|
114
|
+
if (req.method === "POST" && fullUrl === "/api/avatar") {
|
|
115
|
+
var chunks = [];
|
|
116
|
+
var totalSize = 0;
|
|
117
|
+
var maxSize = 2 * 1024 * 1024; // 2MB
|
|
118
|
+
req.on("data", function (chunk) {
|
|
119
|
+
totalSize += chunk.length;
|
|
120
|
+
if (totalSize <= maxSize) chunks.push(chunk);
|
|
121
|
+
});
|
|
122
|
+
req.on("end", function () {
|
|
123
|
+
if (totalSize > maxSize) {
|
|
124
|
+
res.writeHead(413, { "Content-Type": "application/json" });
|
|
125
|
+
res.end('{"error":"File too large (max 2MB)"}');
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
var raw = Buffer.concat(chunks);
|
|
129
|
+
// Detect content type from magic bytes
|
|
130
|
+
var ct = null;
|
|
131
|
+
if (raw[0] === 0xFF && raw[1] === 0xD8) ct = "image/jpeg";
|
|
132
|
+
else if (raw[0] === 0x89 && raw[1] === 0x50) ct = "image/png";
|
|
133
|
+
else if (raw[0] === 0x47 && raw[1] === 0x49) ct = "image/gif";
|
|
134
|
+
else if (raw[0] === 0x52 && raw[1] === 0x49) ct = "image/webp";
|
|
135
|
+
if (!ct) {
|
|
136
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
137
|
+
res.end('{"error":"Unsupported image format"}');
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
var ext = ct.split("/")[1] === "jpeg" ? "jpg" : ct.split("/")[1];
|
|
141
|
+
var avatarDir = path.join(CONFIG_DIR, "avatars");
|
|
142
|
+
fs.mkdirSync(avatarDir, { recursive: true });
|
|
143
|
+
|
|
144
|
+
var userId = "default";
|
|
145
|
+
if (users.isMultiUser()) {
|
|
146
|
+
var mu = getMultiUserFromReq(req);
|
|
147
|
+
if (!mu) {
|
|
148
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
149
|
+
res.end('{"error":"unauthorized"}');
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
userId = mu.id;
|
|
153
|
+
}
|
|
154
|
+
var filename = userId + "." + ext;
|
|
155
|
+
// Remove old avatar files for this user
|
|
156
|
+
try {
|
|
157
|
+
var existing = fs.readdirSync(avatarDir);
|
|
158
|
+
for (var ei = 0; ei < existing.length; ei++) {
|
|
159
|
+
if (existing[ei].startsWith(userId + ".")) {
|
|
160
|
+
fs.unlinkSync(path.join(avatarDir, existing[ei]));
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
} catch (e) {}
|
|
164
|
+
var avatarFilePath = path.join(avatarDir, filename);
|
|
165
|
+
fs.writeFileSync(avatarFilePath, raw);
|
|
166
|
+
try { fs.chmodSync(avatarFilePath, 0o644); } catch (e) {}
|
|
167
|
+
try { fs.chmodSync(avatarDir, 0o755); } catch (e) {}
|
|
168
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
169
|
+
res.end(JSON.stringify({ ok: true, avatar: "/api/avatar/" + userId + "?v=" + Date.now() }));
|
|
170
|
+
});
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Serve custom avatar image
|
|
175
|
+
if (req.method === "GET" && fullUrl.startsWith("/api/avatar/")) {
|
|
176
|
+
var avatarUserId = fullUrl.split("/api/avatar/")[1].split("?")[0];
|
|
177
|
+
var avatarDir = path.join(CONFIG_DIR, "avatars");
|
|
178
|
+
try {
|
|
179
|
+
var files = fs.readdirSync(avatarDir);
|
|
180
|
+
var match = null;
|
|
181
|
+
for (var fi = 0; fi < files.length; fi++) {
|
|
182
|
+
if (files[fi].startsWith(avatarUserId + ".")) {
|
|
183
|
+
match = files[fi];
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (match) {
|
|
188
|
+
var ext = match.split(".").pop();
|
|
189
|
+
var ctMap = { jpg: "image/jpeg", png: "image/png", gif: "image/gif", webp: "image/webp" };
|
|
190
|
+
res.writeHead(200, {
|
|
191
|
+
"Content-Type": ctMap[ext] || "application/octet-stream",
|
|
192
|
+
"Cache-Control": "public, max-age=31536000, immutable",
|
|
193
|
+
});
|
|
194
|
+
res.end(fs.readFileSync(path.join(avatarDir, match)));
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
} catch (e) {}
|
|
198
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
199
|
+
res.end('{"error":"not found"}');
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Upload custom avatar for a mate
|
|
204
|
+
if (req.method === "POST" && fullUrl.startsWith("/api/mate-avatar/")) {
|
|
205
|
+
var mateIdFromUrl = fullUrl.split("/api/mate-avatar/")[1].split("?")[0];
|
|
206
|
+
if (!mateIdFromUrl) {
|
|
207
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
208
|
+
res.end('{"error":"Missing mate ID"}');
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
var chunks = [];
|
|
212
|
+
var totalSize = 0;
|
|
213
|
+
var maxSize = 2 * 1024 * 1024; // 2MB
|
|
214
|
+
req.on("data", function (chunk) {
|
|
215
|
+
totalSize += chunk.length;
|
|
216
|
+
if (totalSize <= maxSize) chunks.push(chunk);
|
|
217
|
+
});
|
|
218
|
+
req.on("end", function () {
|
|
219
|
+
if (totalSize > maxSize) {
|
|
220
|
+
res.writeHead(413, { "Content-Type": "application/json" });
|
|
221
|
+
res.end('{"error":"File too large (max 2MB)"}');
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
var raw = Buffer.concat(chunks);
|
|
225
|
+
var ct = null;
|
|
226
|
+
if (raw[0] === 0xFF && raw[1] === 0xD8) ct = "image/jpeg";
|
|
227
|
+
else if (raw[0] === 0x89 && raw[1] === 0x50) ct = "image/png";
|
|
228
|
+
else if (raw[0] === 0x47 && raw[1] === 0x49) ct = "image/gif";
|
|
229
|
+
else if (raw[0] === 0x52 && raw[1] === 0x49) ct = "image/webp";
|
|
230
|
+
if (!ct) {
|
|
231
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
232
|
+
res.end('{"error":"Unsupported image format"}');
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
var userId = null;
|
|
236
|
+
if (users.isMultiUser()) {
|
|
237
|
+
var mu = getMultiUserFromReq(req);
|
|
238
|
+
if (!mu) {
|
|
239
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
240
|
+
res.end('{"error":"unauthorized"}');
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
userId = mu.id;
|
|
244
|
+
}
|
|
245
|
+
var mateCtx = mates.buildMateCtx(userId);
|
|
246
|
+
var mate = mates.getMate(mateCtx, mateIdFromUrl);
|
|
247
|
+
if (!mate) {
|
|
248
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
249
|
+
res.end('{"error":"Mate not found"}');
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
var ext = ct.split("/")[1] === "jpeg" ? "jpg" : ct.split("/")[1];
|
|
253
|
+
var avatarDir = path.join(CONFIG_DIR, "mate-avatars");
|
|
254
|
+
fs.mkdirSync(avatarDir, { recursive: true });
|
|
255
|
+
var filename = mateIdFromUrl + "." + ext;
|
|
256
|
+
// Remove old avatar files for this mate
|
|
257
|
+
try {
|
|
258
|
+
var existing = fs.readdirSync(avatarDir);
|
|
259
|
+
for (var ei = 0; ei < existing.length; ei++) {
|
|
260
|
+
if (existing[ei].startsWith(mateIdFromUrl + ".")) {
|
|
261
|
+
fs.unlinkSync(path.join(avatarDir, existing[ei]));
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
} catch (e) {}
|
|
265
|
+
var mateAvatarFilePath = path.join(avatarDir, filename);
|
|
266
|
+
fs.writeFileSync(mateAvatarFilePath, raw);
|
|
267
|
+
try { fs.chmodSync(mateAvatarFilePath, 0o644); } catch (e) {}
|
|
268
|
+
try { fs.chmodSync(avatarDir, 0o755); } catch (e) {}
|
|
269
|
+
var avatarPath = "/api/mate-avatar/" + mateIdFromUrl + "?v=" + Date.now();
|
|
270
|
+
// Update mate profile with custom avatar URL
|
|
271
|
+
var profile = mate.profile || {};
|
|
272
|
+
profile.avatarCustom = avatarPath;
|
|
273
|
+
mates.updateMate(mateCtx, mateIdFromUrl, { profile: profile });
|
|
274
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
275
|
+
res.end(JSON.stringify({ ok: true, avatar: avatarPath }));
|
|
276
|
+
});
|
|
277
|
+
return true;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Serve custom mate avatar image
|
|
281
|
+
if (req.method === "GET" && fullUrl.startsWith("/api/mate-avatar/")) {
|
|
282
|
+
var mateAvatarId = fullUrl.split("/api/mate-avatar/")[1].split("?")[0];
|
|
283
|
+
var mateAvatarDir = path.join(CONFIG_DIR, "mate-avatars");
|
|
284
|
+
try {
|
|
285
|
+
var files = fs.readdirSync(mateAvatarDir);
|
|
286
|
+
var match = null;
|
|
287
|
+
for (var fi = 0; fi < files.length; fi++) {
|
|
288
|
+
if (files[fi].startsWith(mateAvatarId + ".")) {
|
|
289
|
+
match = files[fi];
|
|
290
|
+
break;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
if (match) {
|
|
294
|
+
var ext = match.split(".").pop();
|
|
295
|
+
var ctMap = { jpg: "image/jpeg", png: "image/png", gif: "image/gif", webp: "image/webp" };
|
|
296
|
+
res.writeHead(200, {
|
|
297
|
+
"Content-Type": ctMap[ext] || "application/octet-stream",
|
|
298
|
+
"Cache-Control": "public, max-age=31536000, immutable",
|
|
299
|
+
});
|
|
300
|
+
res.end(fs.readFileSync(path.join(mateAvatarDir, match)));
|
|
301
|
+
return true;
|
|
302
|
+
}
|
|
303
|
+
} catch (e) {}
|
|
304
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
305
|
+
res.end('{"error":"not found"}');
|
|
306
|
+
return true;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Change own PIN (multi-user mode)
|
|
310
|
+
if (req.method === "PUT" && fullUrl === "/api/user/pin") {
|
|
311
|
+
if (!users.isMultiUser()) {
|
|
312
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
313
|
+
res.end('{"error":"Not found"}');
|
|
314
|
+
return true;
|
|
315
|
+
}
|
|
316
|
+
var mu = getMultiUserFromReq(req);
|
|
317
|
+
if (!mu) {
|
|
318
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
319
|
+
res.end('{"error":"unauthorized"}');
|
|
320
|
+
return true;
|
|
321
|
+
}
|
|
322
|
+
var body = "";
|
|
323
|
+
req.on("data", function (chunk) { body += chunk; });
|
|
324
|
+
req.on("end", function () {
|
|
325
|
+
try {
|
|
326
|
+
var data = JSON.parse(body);
|
|
327
|
+
if (!data.newPin || typeof data.newPin !== "string" || !/^\d{6}$/.test(data.newPin)) {
|
|
328
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
329
|
+
res.end('{"error":"PIN must be exactly 6 digits"}');
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
var result = users.updateUserPin(mu.id, data.newPin);
|
|
333
|
+
if (result.error) {
|
|
334
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
335
|
+
res.end(JSON.stringify({ error: result.error }));
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
339
|
+
res.end('{"ok":true}');
|
|
340
|
+
} catch (e) {
|
|
341
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
342
|
+
res.end('{"error":"Invalid request"}');
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
return true;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// PUT /api/user/auto-continue
|
|
349
|
+
if (req.method === "PUT" && fullUrl === "/api/user/auto-continue") {
|
|
350
|
+
var mu = getMultiUserFromReq(req);
|
|
351
|
+
if (!mu) {
|
|
352
|
+
// Single-user: use daemon config fallback
|
|
353
|
+
var body = "";
|
|
354
|
+
req.on("data", function (chunk) { body += chunk; });
|
|
355
|
+
req.on("end", function () {
|
|
356
|
+
try {
|
|
357
|
+
var data = JSON.parse(body);
|
|
358
|
+
if (typeof opts.onSetAutoContinue === "function") {
|
|
359
|
+
opts.onSetAutoContinue(!!data.enabled);
|
|
360
|
+
}
|
|
361
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
362
|
+
res.end(JSON.stringify({ ok: true, autoContinueOnRateLimit: !!data.enabled }));
|
|
363
|
+
} catch (e) {
|
|
364
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
365
|
+
res.end('{"error":"Invalid request"}');
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
return true;
|
|
369
|
+
}
|
|
370
|
+
var body = "";
|
|
371
|
+
req.on("data", function (chunk) { body += chunk; });
|
|
372
|
+
req.on("end", function () {
|
|
373
|
+
try {
|
|
374
|
+
var data = JSON.parse(body);
|
|
375
|
+
var result = users.setAutoContinue(mu.id, !!data.enabled);
|
|
376
|
+
if (result.error) {
|
|
377
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
378
|
+
res.end(JSON.stringify({ error: result.error }));
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
382
|
+
res.end(JSON.stringify({ ok: true, autoContinueOnRateLimit: result.autoContinueOnRateLimit }));
|
|
383
|
+
} catch (e) {
|
|
384
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
385
|
+
res.end('{"error":"Invalid request"}');
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
return true;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// PUT /api/user/chat-layout
|
|
392
|
+
if (req.method === "PUT" && fullUrl === "/api/user/chat-layout") {
|
|
393
|
+
var mu = getMultiUserFromReq(req);
|
|
394
|
+
if (!mu) {
|
|
395
|
+
// Single-user: save to daemon config
|
|
396
|
+
var body = "";
|
|
397
|
+
req.on("data", function (chunk) { body += chunk; });
|
|
398
|
+
req.on("end", function () {
|
|
399
|
+
try {
|
|
400
|
+
var data = JSON.parse(body);
|
|
401
|
+
var val = (data.layout === "bubble") ? "bubble" : "channel";
|
|
402
|
+
if (typeof opts.onSetChatLayout === "function") {
|
|
403
|
+
opts.onSetChatLayout(val);
|
|
404
|
+
}
|
|
405
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
406
|
+
res.end(JSON.stringify({ ok: true, chatLayout: val }));
|
|
407
|
+
} catch (e) {
|
|
408
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
409
|
+
res.end('{"error":"Invalid request"}');
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
return true;
|
|
413
|
+
}
|
|
414
|
+
var body = "";
|
|
415
|
+
req.on("data", function (chunk) { body += chunk; });
|
|
416
|
+
req.on("end", function () {
|
|
417
|
+
try {
|
|
418
|
+
var data = JSON.parse(body);
|
|
419
|
+
var result = users.setChatLayout(mu.id, data.layout);
|
|
420
|
+
if (result.error) {
|
|
421
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
422
|
+
res.end(JSON.stringify({ error: result.error }));
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
426
|
+
res.end(JSON.stringify({ ok: true, chatLayout: result.chatLayout }));
|
|
427
|
+
} catch (e) {
|
|
428
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
429
|
+
res.end('{"error":"Invalid request"}');
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
return true;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// POST /api/user/mate-onboarded
|
|
436
|
+
if (req.method === "POST" && fullUrl === "/api/user/mate-onboarded") {
|
|
437
|
+
var mu = getMultiUserFromReq(req);
|
|
438
|
+
if (!mu) {
|
|
439
|
+
// Single-user: save to daemon config
|
|
440
|
+
if (typeof opts.onSetMateOnboarded === "function") {
|
|
441
|
+
opts.onSetMateOnboarded();
|
|
442
|
+
}
|
|
443
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
444
|
+
res.end('{"ok":true}');
|
|
445
|
+
} else {
|
|
446
|
+
users.setMateOnboarded(mu.id);
|
|
447
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
448
|
+
res.end('{"ok":true}');
|
|
449
|
+
}
|
|
450
|
+
return true;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// GET /api/user/auto-continue
|
|
454
|
+
if (req.method === "GET" && fullUrl === "/api/user/auto-continue") {
|
|
455
|
+
var mu = getMultiUserFromReq(req);
|
|
456
|
+
if (!mu) {
|
|
457
|
+
// Single-user: read from daemon config
|
|
458
|
+
var enabled = false;
|
|
459
|
+
if (typeof opts.onGetDaemonConfig === "function") {
|
|
460
|
+
var dc = opts.onGetDaemonConfig();
|
|
461
|
+
enabled = !!dc.autoContinueOnRateLimit;
|
|
462
|
+
}
|
|
463
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
464
|
+
res.end(JSON.stringify({ autoContinueOnRateLimit: enabled }));
|
|
465
|
+
return true;
|
|
466
|
+
}
|
|
467
|
+
var val = users.getAutoContinue(mu.id);
|
|
468
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
469
|
+
res.end(JSON.stringify({ autoContinueOnRateLimit: val }));
|
|
470
|
+
return true;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
return false;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
return { handleRequest: handleRequest };
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
module.exports = { attachSettings: attachSettings };
|