leafrelay 0.2.7 → 0.2.9
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 +30 -4
- package/README.zh-CN.md +30 -4
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +45 -48
- package/dist/cli.js.map +1 -1
- package/dist/daemon.d.ts +1 -0
- package/dist/daemon.js +866 -933
- package/dist/daemon.js.map +1 -1
- package/dist/dist-JyfvbgjD.js +2923 -0
- package/dist/dist-JyfvbgjD.js.map +1 -0
- package/dist/index.d.ts +15 -16
- package/dist/index.js +1 -7
- package/dist/serve-CQnCOCAM.js +6823 -0
- package/dist/serve-CQnCOCAM.js.map +1 -0
- package/package.json +10 -10
- package/dist/chunk-3TTRDNHP.js +0 -3333
- package/dist/chunk-3TTRDNHP.js.map +0 -1
- package/dist/chunk-VXZVIMCG.js +0 -5305
- package/dist/chunk-VXZVIMCG.js.map +0 -1
- package/dist/index.js.map +0 -1
package/dist/daemon.js
CHANGED
|
@@ -1,976 +1,909 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
readProjectSettings,
|
|
10
|
-
removeKnownServer,
|
|
11
|
-
removeServerSession,
|
|
12
|
-
saveKnownServer,
|
|
13
|
-
saveServerSession,
|
|
14
|
-
serverKey,
|
|
15
|
-
setLogSink,
|
|
16
|
-
startServe
|
|
17
|
-
} from "./chunk-3TTRDNHP.js";
|
|
18
|
-
|
|
19
|
-
// ../daemon/src/server.ts
|
|
20
|
-
import { randomBytes, randomUUID as randomUUID2 } from "crypto";
|
|
21
|
-
import { appendFile, chmod, mkdir, readFile, rm, writeFile } from "fs/promises";
|
|
22
|
-
import { createServer } from "net";
|
|
23
|
-
import { formatWithOptions } from "util";
|
|
24
|
-
|
|
1
|
+
import { c as readConfig, d as removeServerSession, f as saveKnownServer, g as startServe, h as setLogSink, i as SocketIOAPI, l as readProjectSettings, m as serverKey, n as HttpRequestScheduler, o as getServerSession, p as saveServerSession, r as RemoteProject, s as normalizeServerUrl, t as ExtendedBaseAPI, u as removeKnownServer } from "./dist-JyfvbgjD.js";
|
|
2
|
+
import { formatWithOptions } from "node:util";
|
|
3
|
+
import { appendFile, chmod, mkdir, realpath, rm, writeFile } from "node:fs/promises";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { join, resolve } from "node:path";
|
|
6
|
+
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
7
|
+
import { createServer } from "node:net";
|
|
8
|
+
import { ResponseError, SocketMessageReader, SocketMessageWriter, createMessageConnection } from "vscode-jsonrpc/node";
|
|
25
9
|
// ../protocol/dist/index.js
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
projectCall: "leafrelay/project/call"
|
|
10
|
+
const RPC_METHOD = {
|
|
11
|
+
initialize: "leafrelay/initialize",
|
|
12
|
+
daemonStatus: "leafrelay/daemon/status",
|
|
13
|
+
daemonShutdown: "leafrelay/daemon/shutdown",
|
|
14
|
+
replicaAttach: "leafrelay/replica/attach",
|
|
15
|
+
replicaDetach: "leafrelay/replica/detach",
|
|
16
|
+
replicaResolveConflict: "leafrelay/replica/resolveConflict",
|
|
17
|
+
replicaRetry: "leafrelay/replica/retry",
|
|
18
|
+
sessionImport: "leafrelay/session/import",
|
|
19
|
+
sessionLogin: "leafrelay/session/login",
|
|
20
|
+
sessionLogout: "leafrelay/session/logout",
|
|
21
|
+
serverList: "leafrelay/server/list",
|
|
22
|
+
serverAdd: "leafrelay/server/add",
|
|
23
|
+
serverRemove: "leafrelay/server/remove",
|
|
24
|
+
serverCall: "leafrelay/server/call",
|
|
25
|
+
projectOpen: "leafrelay/project/open",
|
|
26
|
+
projectClose: "leafrelay/project/close",
|
|
27
|
+
projectCall: "leafrelay/project/call"
|
|
45
28
|
};
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
29
|
+
const RPC_NOTIFICATION = {
|
|
30
|
+
log: "leafrelay/log",
|
|
31
|
+
replicaStatus: "leafrelay/replica/status",
|
|
32
|
+
replicaConflict: "leafrelay/replica/conflict",
|
|
33
|
+
projectEvent: "leafrelay/project/event"
|
|
51
34
|
};
|
|
52
|
-
|
|
53
|
-
// ../daemon/src/server.ts
|
|
54
|
-
import { ResponseError } from "vscode-jsonrpc/node";
|
|
55
|
-
|
|
56
35
|
// ../daemon/src/paths.ts
|
|
57
|
-
import { createHash } from "crypto";
|
|
58
|
-
import { homedir } from "os";
|
|
59
|
-
import { join, resolve } from "path";
|
|
60
36
|
function daemonPaths(environment = process.env) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
};
|
|
37
|
+
const home = environment.LEAFRELAY_HOME ? resolve(environment.LEAFRELAY_HOME) : join(homedir(), ".leafrelay");
|
|
38
|
+
return {
|
|
39
|
+
home,
|
|
40
|
+
socketPath: process.platform === "win32" ? `\\\\.\\pipe\\leafrelay-${createHash("sha256").update(home).digest("hex").slice(0, 16)}` : join(home, "daemon.sock"),
|
|
41
|
+
metadataPath: join(home, "daemon.json"),
|
|
42
|
+
logPath: join(home, "daemon.log"),
|
|
43
|
+
startupLockPath: join(home, "daemon.starting")
|
|
44
|
+
};
|
|
70
45
|
}
|
|
71
|
-
|
|
72
46
|
// ../daemon/src/codec.ts
|
|
73
47
|
function isEncodedBinary(value) {
|
|
74
|
-
|
|
48
|
+
return typeof value === "object" && value !== null && value.$leafrelay === "bytes" && typeof value.base64 === "string";
|
|
75
49
|
}
|
|
76
50
|
function encodeRpcValue(value) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, encodeRpcValue(child)]));
|
|
85
|
-
}
|
|
86
|
-
return value;
|
|
51
|
+
if (value instanceof Uint8Array) return {
|
|
52
|
+
$leafrelay: "bytes",
|
|
53
|
+
base64: Buffer.from(value).toString("base64")
|
|
54
|
+
};
|
|
55
|
+
if (Array.isArray(value)) return value.map(encodeRpcValue);
|
|
56
|
+
if (typeof value === "object" && value !== null) return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, encodeRpcValue(child)]));
|
|
57
|
+
return value;
|
|
87
58
|
}
|
|
88
59
|
function decodeRpcValue(value) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
return value.map(decodeRpcValue);
|
|
94
|
-
}
|
|
95
|
-
if (typeof value === "object" && value !== null) {
|
|
96
|
-
return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, decodeRpcValue(child)]));
|
|
97
|
-
}
|
|
98
|
-
return value;
|
|
60
|
+
if (isEncodedBinary(value)) return Uint8Array.from(Buffer.from(value.base64, "base64"));
|
|
61
|
+
if (Array.isArray(value)) return value.map(decodeRpcValue);
|
|
62
|
+
if (typeof value === "object" && value !== null) return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, decodeRpcValue(child)]));
|
|
63
|
+
return value;
|
|
99
64
|
}
|
|
100
|
-
|
|
101
65
|
// ../daemon/src/networkRuntime.ts
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
66
|
+
const SERVER_OPERATIONS = /* @__PURE__ */ new Set([
|
|
67
|
+
"userProjectsJson",
|
|
68
|
+
"getProjectsJson",
|
|
69
|
+
"projectEntitiesJson",
|
|
70
|
+
"newProject",
|
|
71
|
+
"cloneProject",
|
|
72
|
+
"renameProject",
|
|
73
|
+
"deleteProject",
|
|
74
|
+
"archiveProject",
|
|
75
|
+
"unarchiveProject",
|
|
76
|
+
"trashProject",
|
|
77
|
+
"untrashProject",
|
|
78
|
+
"getFile",
|
|
79
|
+
"addDoc",
|
|
80
|
+
"uploadFile",
|
|
81
|
+
"uploadProject",
|
|
82
|
+
"addFolder",
|
|
83
|
+
"deleteEntity",
|
|
84
|
+
"deleteAuxFiles",
|
|
85
|
+
"renameEntity",
|
|
86
|
+
"moveEntity",
|
|
87
|
+
"compile",
|
|
88
|
+
"stopCompile",
|
|
89
|
+
"indexAll",
|
|
90
|
+
"getMetadata",
|
|
91
|
+
"proxyRequestToSpellingApi",
|
|
92
|
+
"spellingControllerLearn",
|
|
93
|
+
"spellingControllerUnlearn",
|
|
94
|
+
"getProjectSettings",
|
|
95
|
+
"updateProjectSettings",
|
|
96
|
+
"getFileFromClsi",
|
|
97
|
+
"proxySyncPdf",
|
|
98
|
+
"proxySyncCode",
|
|
99
|
+
"getAllTags",
|
|
100
|
+
"createTag",
|
|
101
|
+
"renameTag",
|
|
102
|
+
"deleteTag",
|
|
103
|
+
"addProjectToTag",
|
|
104
|
+
"removeProjectFromTag",
|
|
105
|
+
"proxyToHistoryApiAndGetUpdates",
|
|
106
|
+
"proxyToHistoryApiAndGetFileDiff",
|
|
107
|
+
"proxyToHistoryApiAndGetFileTreeDiff",
|
|
108
|
+
"downloadZipOfVersion",
|
|
109
|
+
"getLabels",
|
|
110
|
+
"createLabel",
|
|
111
|
+
"deleteLabel",
|
|
112
|
+
"getMessages",
|
|
113
|
+
"sendMessage",
|
|
114
|
+
"refreshLinkedFile",
|
|
115
|
+
"createLinkedFile"
|
|
152
116
|
]);
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
117
|
+
const PROJECT_OPERATIONS = /* @__PURE__ */ new Set([
|
|
118
|
+
"init",
|
|
119
|
+
"joinProject",
|
|
120
|
+
"joinDoc",
|
|
121
|
+
"leaveDoc",
|
|
122
|
+
"applyOtUpdate",
|
|
123
|
+
"getConnectedUsers",
|
|
124
|
+
"updatePosition",
|
|
125
|
+
"syncFileChanges"
|
|
162
126
|
]);
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
127
|
+
const REMOTE_PROJECT_OPERATIONS = /* @__PURE__ */ new Set([
|
|
128
|
+
"entry",
|
|
129
|
+
"listEntries",
|
|
130
|
+
"read",
|
|
131
|
+
"write",
|
|
132
|
+
"mkdir",
|
|
133
|
+
"remove",
|
|
134
|
+
"getCurrentVersion",
|
|
135
|
+
"getFileTreeDiff"
|
|
172
136
|
]);
|
|
173
137
|
var NetworkRuntimeRegistry = class {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
read: (path) => this.enqueueProjectOperation(runtime, () => runtime.remote.read(path)),
|
|
408
|
-
write: (path, content) => this.enqueueProjectOperation(runtime, () => runtime.remote.write(path, content)),
|
|
409
|
-
remove: (path) => this.enqueueProjectOperation(runtime, () => runtime.remote.remove(path)),
|
|
410
|
-
getCurrentVersion: () => this.enqueueProjectOperation(runtime, () => runtime.remote.getCurrentVersion()),
|
|
411
|
-
getFileTreeDiff: (from, to) => this.enqueueProjectOperation(runtime, () => runtime.remote.getFileTreeDiff(from, to)),
|
|
412
|
-
onChange: (handler) => runtime.remote.onChange(handler),
|
|
413
|
-
onReconnect: (handler) => runtime.remote.onReconnect(handler)
|
|
414
|
-
};
|
|
415
|
-
}
|
|
416
|
-
enqueueProjectOperation(runtime, operation) {
|
|
417
|
-
const result = runtime.operationQueue.then(operation);
|
|
418
|
-
runtime.operationQueue = result.then(() => void 0, () => void 0);
|
|
419
|
-
return result;
|
|
420
|
-
}
|
|
421
|
-
scheduleProjectStop(runtime) {
|
|
422
|
-
if (runtime.owners.size !== 0 || runtime.replicaReferences !== 0) {
|
|
423
|
-
return;
|
|
424
|
-
}
|
|
425
|
-
void this.stopProject(runtime);
|
|
426
|
-
}
|
|
427
|
-
async stopProject(runtime) {
|
|
428
|
-
if (!this.projects.delete(runtime.key)) {
|
|
429
|
-
return;
|
|
430
|
-
}
|
|
431
|
-
runtime.socket.disconnect();
|
|
432
|
-
}
|
|
138
|
+
events;
|
|
139
|
+
servers = /* @__PURE__ */ new Map();
|
|
140
|
+
projects = /* @__PURE__ */ new Map();
|
|
141
|
+
constructor(events) {
|
|
142
|
+
this.events = events;
|
|
143
|
+
}
|
|
144
|
+
get projectCount() {
|
|
145
|
+
return this.projects.size;
|
|
146
|
+
}
|
|
147
|
+
async listServers() {
|
|
148
|
+
const config = await readConfig();
|
|
149
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(config.knownServers ?? {}), ...Object.keys(config.servers)]);
|
|
150
|
+
if (keys.size === 0) keys.add("www.overleaf.com");
|
|
151
|
+
return [...keys].sort().map((key) => {
|
|
152
|
+
const session = config.servers[key];
|
|
153
|
+
const known = config.knownServers?.[key];
|
|
154
|
+
return {
|
|
155
|
+
name: known?.name ?? key,
|
|
156
|
+
url: known?.url ?? session?.url ?? normalizeServerUrl(key),
|
|
157
|
+
loggedIn: Boolean(session),
|
|
158
|
+
userId: session?.userId,
|
|
159
|
+
userEmail: session?.userEmail
|
|
160
|
+
};
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
async addServer(name, url) {
|
|
164
|
+
await saveKnownServer(name, url);
|
|
165
|
+
}
|
|
166
|
+
async removeServer(server) {
|
|
167
|
+
const key = serverKey(server);
|
|
168
|
+
for (const project of this.projects.values()) if (project.server.key === key) await this.stopProject(project);
|
|
169
|
+
this.servers.delete(key);
|
|
170
|
+
await removeKnownServer(server);
|
|
171
|
+
}
|
|
172
|
+
async login(params) {
|
|
173
|
+
const url = normalizeServerUrl(params.server);
|
|
174
|
+
const scheduler = new HttpRequestScheduler({ onRateLimit: (until) => this.events.log("warn", `${serverKey(url)} is rate limited until ${new Date(until).toISOString()}.`) });
|
|
175
|
+
const api = new ExtendedBaseAPI(url, scheduler);
|
|
176
|
+
const response = params.cookie ? await api.cookiesLogin(params.cookie) : await api.passportLogin(params.email ?? "", params.password ?? "");
|
|
177
|
+
if (response.type !== "success" || !response.identity || !response.userInfo) throw new Error(response.message || `Login to ${url} failed.`);
|
|
178
|
+
const session = {
|
|
179
|
+
url,
|
|
180
|
+
userId: response.userInfo.userId,
|
|
181
|
+
userEmail: response.userInfo.userEmail || params.email || "",
|
|
182
|
+
identity: response.identity,
|
|
183
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
184
|
+
};
|
|
185
|
+
await saveServerSession(session);
|
|
186
|
+
this.servers.set(serverKey(url), {
|
|
187
|
+
key: serverKey(url),
|
|
188
|
+
session,
|
|
189
|
+
scheduler,
|
|
190
|
+
api
|
|
191
|
+
});
|
|
192
|
+
return {
|
|
193
|
+
userId: session.userId,
|
|
194
|
+
userEmail: session.userEmail
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
async importSession(params) {
|
|
198
|
+
const result = await this.login({
|
|
199
|
+
server: params.url,
|
|
200
|
+
cookie: params.identity.cookies
|
|
201
|
+
});
|
|
202
|
+
await saveKnownServer(params.name, params.url);
|
|
203
|
+
return result;
|
|
204
|
+
}
|
|
205
|
+
async logout(server) {
|
|
206
|
+
const runtime = await this.requireServer(server);
|
|
207
|
+
await runtime.api.logout(runtime.session.identity);
|
|
208
|
+
await removeServerSession(server);
|
|
209
|
+
this.servers.delete(runtime.key);
|
|
210
|
+
}
|
|
211
|
+
async callServer(server, operation, args) {
|
|
212
|
+
if (!SERVER_OPERATIONS.has(operation)) throw new Error(`Unsupported server operation: ${operation}.`);
|
|
213
|
+
const runtime = await this.requireServer(server);
|
|
214
|
+
const method = runtime.api[operation];
|
|
215
|
+
if (typeof method !== "function") throw new Error(`Server operation ${operation} is unavailable.`);
|
|
216
|
+
return method.call(runtime.api, runtime.session.identity, ...args);
|
|
217
|
+
}
|
|
218
|
+
async openProject(clientId, server, projectId) {
|
|
219
|
+
const runtime = await this.getOrCreateProject(server, projectId);
|
|
220
|
+
const shared = runtime.owners.size !== 0 || runtime.replicaReferences !== 0;
|
|
221
|
+
runtime.owners.add(clientId);
|
|
222
|
+
await runtime.ready;
|
|
223
|
+
return {
|
|
224
|
+
projectKey: runtime.key,
|
|
225
|
+
shared
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
closeProject(clientId, projectKey) {
|
|
229
|
+
const runtime = this.requireProject(projectKey);
|
|
230
|
+
runtime.owners.delete(clientId);
|
|
231
|
+
this.scheduleProjectStop(runtime);
|
|
232
|
+
}
|
|
233
|
+
async callProject(projectKey, operation, args) {
|
|
234
|
+
const runtime = this.requireProject(projectKey);
|
|
235
|
+
await runtime.ready;
|
|
236
|
+
if (operation.startsWith("remote.")) {
|
|
237
|
+
const remoteOperation = operation.slice(7);
|
|
238
|
+
if (!REMOTE_PROJECT_OPERATIONS.has(remoteOperation)) throw new Error(`Unsupported remote project operation: ${remoteOperation}.`);
|
|
239
|
+
const method = runtime.remote[remoteOperation];
|
|
240
|
+
if (typeof method !== "function") throw new Error(`Remote project operation ${remoteOperation} is unavailable.`);
|
|
241
|
+
return this.enqueueProjectOperation(runtime, () => method.apply(runtime.remote, args));
|
|
242
|
+
}
|
|
243
|
+
if (!PROJECT_OPERATIONS.has(operation)) throw new Error(`Unsupported project operation: ${operation}.`);
|
|
244
|
+
const method = runtime.socket[operation];
|
|
245
|
+
if (typeof method !== "function") throw new Error(`Project operation ${operation} is unavailable.`);
|
|
246
|
+
return method.apply(runtime.socket, args);
|
|
247
|
+
}
|
|
248
|
+
async acquireReplica(server, projectId) {
|
|
249
|
+
const runtime = await this.getOrCreateProject(server, projectId);
|
|
250
|
+
runtime.replicaReferences += 1;
|
|
251
|
+
await runtime.ready;
|
|
252
|
+
let released = false;
|
|
253
|
+
return {
|
|
254
|
+
remote: this.replicaRemote(runtime),
|
|
255
|
+
release: () => {
|
|
256
|
+
if (released) return;
|
|
257
|
+
released = true;
|
|
258
|
+
runtime.replicaReferences -= 1;
|
|
259
|
+
this.scheduleProjectStop(runtime);
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
detachClient(clientId) {
|
|
264
|
+
for (const runtime of this.projects.values()) if (runtime.owners.delete(clientId)) this.scheduleProjectStop(runtime);
|
|
265
|
+
}
|
|
266
|
+
async stopAll() {
|
|
267
|
+
await Promise.all([...this.projects.values()].map((runtime) => this.stopProject(runtime)));
|
|
268
|
+
this.servers.clear();
|
|
269
|
+
}
|
|
270
|
+
async requireServer(server) {
|
|
271
|
+
const key = serverKey(server);
|
|
272
|
+
const existing = this.servers.get(key);
|
|
273
|
+
if (existing) return existing;
|
|
274
|
+
const session = await getServerSession(server);
|
|
275
|
+
if (!session) throw new Error(`Not logged in to ${key}.`);
|
|
276
|
+
const scheduler = new HttpRequestScheduler({ onRateLimit: (until) => this.events.log("warn", `${key} is rate limited until ${new Date(until).toISOString()}.`) });
|
|
277
|
+
const runtime = {
|
|
278
|
+
key,
|
|
279
|
+
session,
|
|
280
|
+
scheduler,
|
|
281
|
+
api: new ExtendedBaseAPI(session.url, scheduler)
|
|
282
|
+
};
|
|
283
|
+
this.servers.set(key, runtime);
|
|
284
|
+
return runtime;
|
|
285
|
+
}
|
|
286
|
+
async getOrCreateProject(server, projectId) {
|
|
287
|
+
const serverRuntime = await this.requireServer(server);
|
|
288
|
+
const key = `${serverRuntime.key}:${projectId}`;
|
|
289
|
+
const existing = this.projects.get(key);
|
|
290
|
+
if (existing) return existing;
|
|
291
|
+
const socket = new SocketIOAPI(serverRuntime.session.url, serverRuntime.api, serverRuntime.session.identity, projectId);
|
|
292
|
+
const remote = new RemoteProject(serverRuntime.session.url, projectId, serverRuntime.session.identity.cookies, {
|
|
293
|
+
api: serverRuntime.api,
|
|
294
|
+
identity: serverRuntime.session.identity,
|
|
295
|
+
socket
|
|
296
|
+
});
|
|
297
|
+
const runtime = {
|
|
298
|
+
key,
|
|
299
|
+
server: serverRuntime,
|
|
300
|
+
projectId,
|
|
301
|
+
socket,
|
|
302
|
+
remote,
|
|
303
|
+
ready: Promise.resolve(),
|
|
304
|
+
owners: /* @__PURE__ */ new Set(),
|
|
305
|
+
replicaReferences: 0,
|
|
306
|
+
operationQueue: Promise.resolve()
|
|
307
|
+
};
|
|
308
|
+
runtime.ready = remote.connect().then(() => this.registerProjectEvents(runtime)).catch((error) => {
|
|
309
|
+
this.projects.delete(key);
|
|
310
|
+
socket.disconnect();
|
|
311
|
+
throw error;
|
|
312
|
+
});
|
|
313
|
+
this.projects.set(key, runtime);
|
|
314
|
+
return runtime;
|
|
315
|
+
}
|
|
316
|
+
registerProjectEvents(runtime) {
|
|
317
|
+
const emit = (event, ...args) => this.events.projectEvent(runtime.owners, {
|
|
318
|
+
projectKey: runtime.key,
|
|
319
|
+
event,
|
|
320
|
+
args
|
|
321
|
+
});
|
|
322
|
+
runtime.socket.updateEventHandlers({
|
|
323
|
+
onFileCreated: (...args) => emit("onFileCreated", ...args),
|
|
324
|
+
onFileRenamed: (...args) => emit("onFileRenamed", ...args),
|
|
325
|
+
onFileRemoved: (...args) => emit("onFileRemoved", ...args),
|
|
326
|
+
onFileMoved: (...args) => emit("onFileMoved", ...args),
|
|
327
|
+
onFileChanged: (...args) => emit("onFileChanged", ...args),
|
|
328
|
+
onDisconnected: (reason) => emit("onDisconnected", reason),
|
|
329
|
+
onConnectionAccepted: (...args) => emit("onConnectionAccepted", ...args),
|
|
330
|
+
onClientUpdated: (...args) => emit("onClientUpdated", ...args),
|
|
331
|
+
onClientDisconnected: (...args) => emit("onClientDisconnected", ...args),
|
|
332
|
+
onReceivedMessage: (...args) => emit("onReceivedMessage", ...args),
|
|
333
|
+
onSpellCheckLanguageUpdated: (...args) => emit("onSpellCheckLanguageUpdated", ...args),
|
|
334
|
+
onCompilerUpdated: (...args) => emit("onCompilerUpdated", ...args),
|
|
335
|
+
onRootDocUpdated: (...args) => emit("onRootDocUpdated", ...args)
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
requireProject(projectKey) {
|
|
339
|
+
const runtime = this.projects.get(projectKey);
|
|
340
|
+
if (!runtime) throw new Error(`Unknown project runtime ${projectKey}.`);
|
|
341
|
+
return runtime;
|
|
342
|
+
}
|
|
343
|
+
replicaRemote(runtime) {
|
|
344
|
+
return {
|
|
345
|
+
connect: async () => {},
|
|
346
|
+
disconnect: () => {},
|
|
347
|
+
listEntries: () => runtime.remote.listEntries(),
|
|
348
|
+
entry: (path) => runtime.remote.entry(path),
|
|
349
|
+
read: (path) => this.enqueueProjectOperation(runtime, () => runtime.remote.read(path)),
|
|
350
|
+
write: (path, content) => this.enqueueProjectOperation(runtime, () => runtime.remote.write(path, content)),
|
|
351
|
+
remove: (path) => this.enqueueProjectOperation(runtime, () => runtime.remote.remove(path)),
|
|
352
|
+
getCurrentVersion: () => this.enqueueProjectOperation(runtime, () => runtime.remote.getCurrentVersion()),
|
|
353
|
+
getFileTreeDiff: (from, to) => this.enqueueProjectOperation(runtime, () => runtime.remote.getFileTreeDiff(from, to)),
|
|
354
|
+
onChange: (handler) => runtime.remote.onChange(handler),
|
|
355
|
+
onReconnect: (handler) => runtime.remote.onReconnect(handler)
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
enqueueProjectOperation(runtime, operation) {
|
|
359
|
+
const result = runtime.operationQueue.then(operation);
|
|
360
|
+
runtime.operationQueue = result.then(() => void 0, () => void 0);
|
|
361
|
+
return result;
|
|
362
|
+
}
|
|
363
|
+
scheduleProjectStop(runtime) {
|
|
364
|
+
if (runtime.owners.size !== 0 || runtime.replicaReferences !== 0) return;
|
|
365
|
+
this.stopProject(runtime);
|
|
366
|
+
}
|
|
367
|
+
async stopProject(runtime) {
|
|
368
|
+
if (!this.projects.delete(runtime.key)) return;
|
|
369
|
+
runtime.socket.disconnect();
|
|
370
|
+
}
|
|
433
371
|
};
|
|
434
|
-
|
|
435
372
|
// ../daemon/src/replicaRegistry.ts
|
|
436
|
-
import { randomUUID } from "crypto";
|
|
437
|
-
import { realpath } from "fs/promises";
|
|
438
|
-
import { resolve as resolve2 } from "path";
|
|
439
373
|
var ReplicaAlreadyActiveError = class extends Error {
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
374
|
+
projectId;
|
|
375
|
+
activeRoot;
|
|
376
|
+
requestedRoot;
|
|
377
|
+
code = "ReplicaAlreadyActive";
|
|
378
|
+
constructor(projectId, activeRoot, requestedRoot) {
|
|
379
|
+
super(`Overleaf project ${projectId} is already synchronized by ${activeRoot}; ${requestedRoot} cannot become a second writable replica.`);
|
|
380
|
+
this.projectId = projectId;
|
|
381
|
+
this.activeRoot = activeRoot;
|
|
382
|
+
this.requestedRoot = requestedRoot;
|
|
383
|
+
}
|
|
450
384
|
};
|
|
451
385
|
var ReplicaRegistry = class {
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
386
|
+
events;
|
|
387
|
+
start;
|
|
388
|
+
byId = /* @__PURE__ */ new Map();
|
|
389
|
+
byRoot = /* @__PURE__ */ new Map();
|
|
390
|
+
byProject = /* @__PURE__ */ new Map();
|
|
391
|
+
projectOperations = /* @__PURE__ */ new Map();
|
|
392
|
+
clients = /* @__PURE__ */ new Set();
|
|
393
|
+
constructor(events, start = startServe) {
|
|
394
|
+
this.events = events;
|
|
395
|
+
this.start = start;
|
|
396
|
+
}
|
|
397
|
+
get size() {
|
|
398
|
+
return this.byId.size;
|
|
399
|
+
}
|
|
400
|
+
registerClient(clientId) {
|
|
401
|
+
this.clients.add(clientId);
|
|
402
|
+
}
|
|
403
|
+
async attach(clientId, params) {
|
|
404
|
+
const root = await realpath(resolve(params.directory));
|
|
405
|
+
const settings = await readProjectSettings(root);
|
|
406
|
+
const projectKey = `${settings.serverName}\0${settings.projectId}`;
|
|
407
|
+
return this.withProjectLock(projectKey, () => this.attachResolved(clientId, params, root, settings, projectKey));
|
|
408
|
+
}
|
|
409
|
+
async attachResolved(clientId, params, root, settings, projectKey) {
|
|
410
|
+
if (!this.clients.has(clientId)) throw new Error("The replica client disconnected before attachment started.");
|
|
411
|
+
const rootEntry = this.byRoot.get(root);
|
|
412
|
+
if (rootEntry) {
|
|
413
|
+
if (rootEntry.projectKey !== projectKey) throw new Error(`${root} is already registered for another Overleaf project.`);
|
|
414
|
+
this.addOwner(rootEntry, clientId);
|
|
415
|
+
return this.result(rootEntry, true);
|
|
416
|
+
}
|
|
417
|
+
const projectEntry = this.byProject.get(projectKey);
|
|
418
|
+
if (projectEntry) throw new ReplicaAlreadyActiveError(settings.projectId, projectEntry.root, root);
|
|
419
|
+
const id = randomUUID();
|
|
420
|
+
const owners = /* @__PURE__ */ new Set([clientId]);
|
|
421
|
+
const status = { state: "starting" };
|
|
422
|
+
let entry;
|
|
423
|
+
this.events.status(owners, {
|
|
424
|
+
replicaId: id,
|
|
425
|
+
status
|
|
426
|
+
});
|
|
427
|
+
this.events.log(`[${settings.projectName}] Starting synchronization for ${root}.`);
|
|
428
|
+
try {
|
|
429
|
+
const running = await this.start(root, {
|
|
430
|
+
cookie: params.cookie,
|
|
431
|
+
onSyncError: (message) => {
|
|
432
|
+
if (!entry) return;
|
|
433
|
+
entry.status = message ? {
|
|
434
|
+
state: "error",
|
|
435
|
+
message
|
|
436
|
+
} : entry.running.conflicts().length ? { state: "paused" } : { state: "active" };
|
|
437
|
+
this.events.status(entry.owners, {
|
|
438
|
+
replicaId: id,
|
|
439
|
+
status: entry.status
|
|
440
|
+
});
|
|
441
|
+
},
|
|
442
|
+
log: (message) => this.events.log(`[${settings.projectName}] ${message}`),
|
|
443
|
+
onConflict: (path, reason) => {
|
|
444
|
+
if (!entry) return;
|
|
445
|
+
entry.status = {
|
|
446
|
+
state: "paused",
|
|
447
|
+
message: `Conflict in ${path}`
|
|
448
|
+
};
|
|
449
|
+
this.events.conflict(entry.owners, {
|
|
450
|
+
replicaId: entry.id,
|
|
451
|
+
path,
|
|
452
|
+
reason,
|
|
453
|
+
conflicts: entry.running.conflicts()
|
|
454
|
+
});
|
|
455
|
+
this.events.status(entry.owners, {
|
|
456
|
+
replicaId: entry.id,
|
|
457
|
+
status: entry.status
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
if (!this.clients.has(clientId)) {
|
|
462
|
+
await running.stop();
|
|
463
|
+
this.events.empty();
|
|
464
|
+
throw new Error("The replica client disconnected before attachment completed.");
|
|
465
|
+
}
|
|
466
|
+
entry = {
|
|
467
|
+
id,
|
|
468
|
+
root,
|
|
469
|
+
projectKey,
|
|
470
|
+
serverName: settings.serverName,
|
|
471
|
+
projectId: settings.projectId,
|
|
472
|
+
projectName: settings.projectName,
|
|
473
|
+
owners,
|
|
474
|
+
running,
|
|
475
|
+
status: running.syncError() ? {
|
|
476
|
+
state: "error",
|
|
477
|
+
message: running.syncError()
|
|
478
|
+
} : running.conflicts().length === 0 ? { state: "active" } : { state: "paused" }
|
|
479
|
+
};
|
|
480
|
+
this.byId.set(id, entry);
|
|
481
|
+
this.byRoot.set(root, entry);
|
|
482
|
+
this.byProject.set(projectKey, entry);
|
|
483
|
+
this.events.status(owners, {
|
|
484
|
+
replicaId: id,
|
|
485
|
+
status: entry.status
|
|
486
|
+
});
|
|
487
|
+
return this.result(entry, false);
|
|
488
|
+
} catch (error) {
|
|
489
|
+
const failed = {
|
|
490
|
+
state: "error",
|
|
491
|
+
message: error instanceof Error ? error.message : String(error)
|
|
492
|
+
};
|
|
493
|
+
this.events.status(owners, {
|
|
494
|
+
replicaId: id,
|
|
495
|
+
status: failed
|
|
496
|
+
});
|
|
497
|
+
throw error;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
async detach(clientId, replicaId) {
|
|
501
|
+
const entry = this.require(replicaId);
|
|
502
|
+
await this.withProjectLock(entry.projectKey, async () => {
|
|
503
|
+
entry.owners.delete(clientId);
|
|
504
|
+
if (entry.owners.size === 0) await this.stop(entry);
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
async detachClient(clientId) {
|
|
508
|
+
this.clients.delete(clientId);
|
|
509
|
+
const owned = [...this.byId.values()].filter((entry) => entry.owners.has(clientId));
|
|
510
|
+
await Promise.all(owned.map((entry) => this.withProjectLock(entry.projectKey, async () => {
|
|
511
|
+
entry.owners.delete(clientId);
|
|
512
|
+
if (entry.owners.size === 0) await this.stop(entry);
|
|
513
|
+
})));
|
|
514
|
+
}
|
|
515
|
+
async resolveConflict(replicaId, path, winner) {
|
|
516
|
+
const entry = this.require(replicaId);
|
|
517
|
+
await entry.running.resolveConflict(path, winner);
|
|
518
|
+
entry.status = entry.running.syncError() ? {
|
|
519
|
+
state: "error",
|
|
520
|
+
message: entry.running.syncError()
|
|
521
|
+
} : entry.running.conflicts().length === 0 ? { state: "active" } : { state: "paused" };
|
|
522
|
+
this.events.status(entry.owners, {
|
|
523
|
+
replicaId,
|
|
524
|
+
status: entry.status
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
async retry(replicaId, path) {
|
|
528
|
+
const entry = this.require(replicaId);
|
|
529
|
+
await entry.running.retry(path);
|
|
530
|
+
entry.status = entry.running.syncError() ? {
|
|
531
|
+
state: "error",
|
|
532
|
+
message: entry.running.syncError()
|
|
533
|
+
} : entry.running.conflicts().length === 0 ? { state: "active" } : { state: "paused" };
|
|
534
|
+
this.events.status(entry.owners, {
|
|
535
|
+
replicaId,
|
|
536
|
+
status: entry.status
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
list() {
|
|
540
|
+
return [...this.byId.values()].map((entry) => ({
|
|
541
|
+
id: entry.id,
|
|
542
|
+
root: entry.root,
|
|
543
|
+
serverName: entry.serverName,
|
|
544
|
+
projectId: entry.projectId,
|
|
545
|
+
projectName: entry.projectName,
|
|
546
|
+
clients: entry.owners.size,
|
|
547
|
+
status: entry.status,
|
|
548
|
+
conflicts: entry.running.conflicts()
|
|
549
|
+
}));
|
|
550
|
+
}
|
|
551
|
+
async stopAll() {
|
|
552
|
+
await Promise.all([...this.byId.values()].map((entry) => this.stop(entry)));
|
|
553
|
+
}
|
|
554
|
+
addOwner(entry, clientId) {
|
|
555
|
+
entry.owners.add(clientId);
|
|
556
|
+
}
|
|
557
|
+
async stop(entry) {
|
|
558
|
+
if (!this.byId.delete(entry.id)) return;
|
|
559
|
+
this.byRoot.delete(entry.root);
|
|
560
|
+
this.byProject.delete(entry.projectKey);
|
|
561
|
+
entry.status = { state: "stopping" };
|
|
562
|
+
this.events.status(entry.owners, {
|
|
563
|
+
replicaId: entry.id,
|
|
564
|
+
status: entry.status
|
|
565
|
+
});
|
|
566
|
+
await entry.running.stop();
|
|
567
|
+
this.events.empty();
|
|
568
|
+
}
|
|
569
|
+
require(replicaId) {
|
|
570
|
+
const entry = this.byId.get(replicaId);
|
|
571
|
+
if (!entry) throw new Error(`Unknown replica ${replicaId}.`);
|
|
572
|
+
return entry;
|
|
573
|
+
}
|
|
574
|
+
result(entry, shared) {
|
|
575
|
+
return {
|
|
576
|
+
replicaId: entry.id,
|
|
577
|
+
root: entry.root,
|
|
578
|
+
serverName: entry.serverName,
|
|
579
|
+
projectId: entry.projectId,
|
|
580
|
+
projectName: entry.projectName,
|
|
581
|
+
shared,
|
|
582
|
+
status: entry.status,
|
|
583
|
+
conflicts: entry.running.conflicts()
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
async withProjectLock(projectKey, operation) {
|
|
587
|
+
const previous = this.projectOperations.get(projectKey) ?? Promise.resolve();
|
|
588
|
+
let release;
|
|
589
|
+
const current = new Promise((resolveOperation) => {
|
|
590
|
+
release = resolveOperation;
|
|
591
|
+
});
|
|
592
|
+
this.projectOperations.set(projectKey, current);
|
|
593
|
+
await previous;
|
|
594
|
+
try {
|
|
595
|
+
return await operation();
|
|
596
|
+
} finally {
|
|
597
|
+
release();
|
|
598
|
+
if (this.projectOperations.get(projectKey) === current) this.projectOperations.delete(projectKey);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
645
601
|
};
|
|
646
|
-
|
|
647
602
|
// ../daemon/src/transport.ts
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
"EPIPE",
|
|
655
|
-
"ECONNRESET",
|
|
656
|
-
"ERR_SOCKET_CLOSED",
|
|
657
|
-
"ERR_STREAM_DESTROYED",
|
|
658
|
-
"ERR_STREAM_WRITE_AFTER_END"
|
|
603
|
+
const CLOSED_SOCKET_ERRORS = /* @__PURE__ */ new Set([
|
|
604
|
+
"EPIPE",
|
|
605
|
+
"ECONNRESET",
|
|
606
|
+
"ERR_SOCKET_CLOSED",
|
|
607
|
+
"ERR_STREAM_DESTROYED",
|
|
608
|
+
"ERR_STREAM_WRITE_AFTER_END"
|
|
659
609
|
]);
|
|
660
610
|
var LeafRelaySocketWriter = class extends SocketMessageWriter {
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
}
|
|
676
|
-
}
|
|
611
|
+
transportSocket;
|
|
612
|
+
constructor(transportSocket) {
|
|
613
|
+
super(transportSocket);
|
|
614
|
+
this.transportSocket = transportSocket;
|
|
615
|
+
}
|
|
616
|
+
async write(message) {
|
|
617
|
+
try {
|
|
618
|
+
await super.write(message);
|
|
619
|
+
} catch (error) {
|
|
620
|
+
const code = error.code;
|
|
621
|
+
if (this.transportSocket.destroyed || this.transportSocket.writableEnded || code && CLOSED_SOCKET_ERRORS.has(code)) return;
|
|
622
|
+
throw error;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
677
625
|
};
|
|
678
626
|
function createSocketRpcConnection(socket) {
|
|
679
|
-
|
|
627
|
+
return createMessageConnection(new SocketMessageReader(socket), new LeafRelaySocketWriter(socket));
|
|
680
628
|
}
|
|
681
|
-
|
|
682
629
|
// ../daemon/src/server.ts
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
630
|
+
const IDLE_EXIT_MS = 6e4;
|
|
631
|
+
const REPLICA_ALREADY_ACTIVE = -32010;
|
|
632
|
+
const daemonVersion = "0.2.9";
|
|
686
633
|
var LeafRelayDaemonServer = class {
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
await this.network.stopAll();
|
|
939
|
-
setLogSink(void 0);
|
|
940
|
-
for (const client of this.clients) {
|
|
941
|
-
client.rpc.dispose();
|
|
942
|
-
client.socket.destroy();
|
|
943
|
-
}
|
|
944
|
-
this.clients.clear();
|
|
945
|
-
if (this.server) {
|
|
946
|
-
await new Promise((resolveClose) => this.server.close(() => resolveClose()));
|
|
947
|
-
}
|
|
948
|
-
await rm(this.paths.metadataPath, { force: true });
|
|
949
|
-
if (process.platform !== "win32") {
|
|
950
|
-
await rm(this.paths.socketPath, { force: true });
|
|
951
|
-
}
|
|
952
|
-
}
|
|
634
|
+
options;
|
|
635
|
+
paths;
|
|
636
|
+
token;
|
|
637
|
+
startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
638
|
+
clients = /* @__PURE__ */ new Set();
|
|
639
|
+
network;
|
|
640
|
+
replicas;
|
|
641
|
+
server;
|
|
642
|
+
idleTimer;
|
|
643
|
+
stopping;
|
|
644
|
+
constructor(options = {}) {
|
|
645
|
+
this.options = options;
|
|
646
|
+
this.paths = options.paths ?? daemonPaths();
|
|
647
|
+
this.token = options.token ?? randomBytes(32).toString("hex");
|
|
648
|
+
this.network = new NetworkRuntimeRegistry({
|
|
649
|
+
projectEvent: (owners, notification) => this.notifyOwners(owners, RPC_NOTIFICATION.projectEvent, notification),
|
|
650
|
+
log: (level, message) => void this.log(level, message)
|
|
651
|
+
});
|
|
652
|
+
this.replicas = new ReplicaRegistry({
|
|
653
|
+
status: (owners, notification) => this.notifyOwners(owners, RPC_NOTIFICATION.replicaStatus, notification),
|
|
654
|
+
conflict: (owners, notification) => this.notifyOwners(owners, RPC_NOTIFICATION.replicaConflict, notification),
|
|
655
|
+
empty: () => this.scheduleIdleExit(),
|
|
656
|
+
log: (message) => void this.log("info", message)
|
|
657
|
+
}, async (directory, startOptions) => {
|
|
658
|
+
const settings = await readProjectSettings(directory);
|
|
659
|
+
const lease = await this.network.acquireReplica(settings.serverName, settings.projectId);
|
|
660
|
+
try {
|
|
661
|
+
const running = await startServe(directory, {
|
|
662
|
+
...startOptions,
|
|
663
|
+
remote: lease.remote
|
|
664
|
+
});
|
|
665
|
+
return {
|
|
666
|
+
...running,
|
|
667
|
+
stop: async () => {
|
|
668
|
+
await running.stop();
|
|
669
|
+
lease.release();
|
|
670
|
+
}
|
|
671
|
+
};
|
|
672
|
+
} catch (error) {
|
|
673
|
+
lease.release();
|
|
674
|
+
throw error;
|
|
675
|
+
}
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
async start() {
|
|
679
|
+
await mkdir(this.paths.home, {
|
|
680
|
+
recursive: true,
|
|
681
|
+
mode: 448
|
|
682
|
+
});
|
|
683
|
+
setLogSink((level, args) => void this.log(level, formatWithOptions({
|
|
684
|
+
depth: 5,
|
|
685
|
+
breakLength: Infinity
|
|
686
|
+
}, ...args)));
|
|
687
|
+
if (process.platform !== "win32") await rm(this.paths.socketPath, { force: true });
|
|
688
|
+
this.server = createServer((socket) => this.accept(socket));
|
|
689
|
+
await new Promise((resolveListen, reject) => {
|
|
690
|
+
this.server.once("error", reject);
|
|
691
|
+
this.server.listen(this.paths.socketPath, () => {
|
|
692
|
+
this.server.off("error", reject);
|
|
693
|
+
resolveListen();
|
|
694
|
+
});
|
|
695
|
+
});
|
|
696
|
+
if (process.platform !== "win32") await chmod(this.paths.socketPath, 384);
|
|
697
|
+
const metadata = {
|
|
698
|
+
pid: process.pid,
|
|
699
|
+
socketPath: this.paths.socketPath,
|
|
700
|
+
token: this.token,
|
|
701
|
+
protocolVersion: 1,
|
|
702
|
+
version: daemonVersion,
|
|
703
|
+
startedAt: this.startedAt
|
|
704
|
+
};
|
|
705
|
+
await writeFile(this.paths.metadataPath, `${JSON.stringify(metadata, null, 2)}\n`, {
|
|
706
|
+
encoding: "utf8",
|
|
707
|
+
mode: 384
|
|
708
|
+
});
|
|
709
|
+
await chmod(this.paths.metadataPath, 384);
|
|
710
|
+
await this.log("info", `Daemon ${daemonVersion} listening on ${this.paths.socketPath}.`);
|
|
711
|
+
this.scheduleIdleExit();
|
|
712
|
+
return metadata;
|
|
713
|
+
}
|
|
714
|
+
async stop() {
|
|
715
|
+
if (this.stopping) return this.stopping;
|
|
716
|
+
this.stopping = this.performStop();
|
|
717
|
+
return this.stopping;
|
|
718
|
+
}
|
|
719
|
+
status() {
|
|
720
|
+
return {
|
|
721
|
+
pid: process.pid,
|
|
722
|
+
version: daemonVersion,
|
|
723
|
+
protocolVersion: 1,
|
|
724
|
+
startedAt: this.startedAt,
|
|
725
|
+
clients: this.clients.size,
|
|
726
|
+
projects: this.network.projectCount,
|
|
727
|
+
replicas: this.replicas.list()
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
accept(socket) {
|
|
731
|
+
this.cancelIdleExit();
|
|
732
|
+
const rpc = createSocketRpcConnection(socket);
|
|
733
|
+
const client = {
|
|
734
|
+
leaseId: randomUUID(),
|
|
735
|
+
socket,
|
|
736
|
+
rpc,
|
|
737
|
+
initialized: false
|
|
738
|
+
};
|
|
739
|
+
this.clients.add(client);
|
|
740
|
+
rpc.onRequest(RPC_METHOD.initialize, (params) => this.initialize(client, params));
|
|
741
|
+
rpc.onRequest(RPC_METHOD.daemonStatus, () => this.authorized(client, () => this.status()));
|
|
742
|
+
rpc.onRequest(RPC_METHOD.daemonShutdown, () => this.authorized(client, async () => {
|
|
743
|
+
setTimeout(() => void this.stop(), 25);
|
|
744
|
+
return null;
|
|
745
|
+
}));
|
|
746
|
+
rpc.onRequest(RPC_METHOD.replicaAttach, (params) => this.authorized(client, async () => {
|
|
747
|
+
try {
|
|
748
|
+
return await this.replicas.attach(client.leaseId, params);
|
|
749
|
+
} catch (error) {
|
|
750
|
+
if (error instanceof ReplicaAlreadyActiveError) throw new ResponseError(REPLICA_ALREADY_ACTIVE, error.message, {
|
|
751
|
+
code: error.code,
|
|
752
|
+
projectId: error.projectId,
|
|
753
|
+
activeRoot: error.activeRoot,
|
|
754
|
+
requestedRoot: error.requestedRoot
|
|
755
|
+
});
|
|
756
|
+
throw error;
|
|
757
|
+
}
|
|
758
|
+
}));
|
|
759
|
+
rpc.onRequest(RPC_METHOD.replicaDetach, (params) => this.authorized(client, async () => {
|
|
760
|
+
await this.replicas.detach(client.leaseId, params.replicaId);
|
|
761
|
+
return null;
|
|
762
|
+
}));
|
|
763
|
+
rpc.onRequest(RPC_METHOD.replicaResolveConflict, (params) => this.authorized(client, async () => {
|
|
764
|
+
await this.replicas.resolveConflict(params.replicaId, params.path, params.winner);
|
|
765
|
+
return null;
|
|
766
|
+
}));
|
|
767
|
+
rpc.onRequest(RPC_METHOD.replicaRetry, (params) => this.authorized(client, async () => {
|
|
768
|
+
await this.replicas.retry(params.replicaId, params.path);
|
|
769
|
+
return null;
|
|
770
|
+
}));
|
|
771
|
+
rpc.onRequest(RPC_METHOD.serverList, () => this.authorized(client, () => this.network.listServers()));
|
|
772
|
+
rpc.onRequest(RPC_METHOD.serverAdd, (params) => this.authorized(client, async () => {
|
|
773
|
+
await this.network.addServer(params.name, params.url);
|
|
774
|
+
return null;
|
|
775
|
+
}));
|
|
776
|
+
rpc.onRequest(RPC_METHOD.serverRemove, (params) => this.authorized(client, async () => {
|
|
777
|
+
await this.network.removeServer(params.server);
|
|
778
|
+
return null;
|
|
779
|
+
}));
|
|
780
|
+
rpc.onRequest(RPC_METHOD.sessionLogin, (params) => this.authorized(client, () => this.network.login(params)));
|
|
781
|
+
rpc.onRequest(RPC_METHOD.sessionImport, (params) => this.authorized(client, () => this.network.importSession(params)));
|
|
782
|
+
rpc.onRequest(RPC_METHOD.sessionLogout, (params) => this.authorized(client, async () => {
|
|
783
|
+
await this.network.logout(params.server);
|
|
784
|
+
return null;
|
|
785
|
+
}));
|
|
786
|
+
rpc.onRequest(RPC_METHOD.serverCall, (params) => this.authorized(client, async () => {
|
|
787
|
+
return encodeRpcValue(await this.network.callServer(params.server, params.operation, decodeRpcValue(params.args)));
|
|
788
|
+
}));
|
|
789
|
+
rpc.onRequest(RPC_METHOD.projectOpen, (params) => this.authorized(client, () => this.network.openProject(client.leaseId, params.server, params.projectId)));
|
|
790
|
+
rpc.onRequest(RPC_METHOD.projectClose, (params) => this.authorized(client, () => {
|
|
791
|
+
this.network.closeProject(client.leaseId, params.projectKey);
|
|
792
|
+
return null;
|
|
793
|
+
}));
|
|
794
|
+
rpc.onRequest(RPC_METHOD.projectCall, (params) => this.authorized(client, async () => {
|
|
795
|
+
return encodeRpcValue(await await this.network.callProject(params.projectKey, params.operation, decodeRpcValue(params.args)));
|
|
796
|
+
}));
|
|
797
|
+
rpc.onClose(() => void this.closeClient(client));
|
|
798
|
+
rpc.onError((error) => void this.log("error", `JSON-RPC connection error: ${String(error[0])}`));
|
|
799
|
+
rpc.listen();
|
|
800
|
+
}
|
|
801
|
+
initialize(client, params) {
|
|
802
|
+
if (params.token !== this.token) throw new ResponseError(-32001, "Daemon authentication failed.");
|
|
803
|
+
if (params.protocolVersion !== 1) throw new ResponseError(-32002, `Protocol ${params.protocolVersion} is incompatible with daemon protocol 1.`);
|
|
804
|
+
if (!params.clientId) throw new ResponseError(-32602, "clientId is required.");
|
|
805
|
+
client.clientId = params.clientId;
|
|
806
|
+
client.initialized = true;
|
|
807
|
+
this.replicas.registerClient(client.leaseId);
|
|
808
|
+
this.log("info", `${params.clientName} ${params.clientVersion} connected (${params.clientId}).`);
|
|
809
|
+
return {
|
|
810
|
+
daemonVersion,
|
|
811
|
+
protocolVersion: 1,
|
|
812
|
+
pid: process.pid,
|
|
813
|
+
startedAt: this.startedAt
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
async authorized(client, operation) {
|
|
817
|
+
if (!client.initialized) throw new ResponseError(-32001, "Initialize the daemon connection first.");
|
|
818
|
+
try {
|
|
819
|
+
return await operation();
|
|
820
|
+
} catch (error) {
|
|
821
|
+
if (error instanceof ResponseError) throw error;
|
|
822
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
823
|
+
await this.log("error", `RPC operation failed: ${formatWithOptions({
|
|
824
|
+
depth: 5,
|
|
825
|
+
breakLength: Infinity
|
|
826
|
+
}, error)}`);
|
|
827
|
+
throw new ResponseError(-32603, message);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
async closeClient(client) {
|
|
831
|
+
if (!this.clients.delete(client)) return;
|
|
832
|
+
if (client.clientId) try {
|
|
833
|
+
await this.replicas.detachClient(client.leaseId);
|
|
834
|
+
} catch (error) {
|
|
835
|
+
await this.log("error", `Failed to stop replicas for a disconnected client: ${error instanceof Error ? error.message : String(error)}`);
|
|
836
|
+
} finally {
|
|
837
|
+
this.network.detachClient(client.leaseId);
|
|
838
|
+
}
|
|
839
|
+
this.scheduleIdleExit();
|
|
840
|
+
}
|
|
841
|
+
notifyOwners(owners, method, notification) {
|
|
842
|
+
for (const client of this.clients) if (owners.has(client.leaseId)) this.notifyClient(client, method, notification);
|
|
843
|
+
}
|
|
844
|
+
async log(level, message) {
|
|
845
|
+
const notification = {
|
|
846
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
847
|
+
level,
|
|
848
|
+
message
|
|
849
|
+
};
|
|
850
|
+
await appendFile(this.paths.logPath, `${notification.timestamp} [${level}] ${message}\n`, {
|
|
851
|
+
encoding: "utf8",
|
|
852
|
+
mode: 384
|
|
853
|
+
});
|
|
854
|
+
for (const client of this.clients) if (client.initialized) this.notifyClient(client, RPC_NOTIFICATION.log, notification);
|
|
855
|
+
}
|
|
856
|
+
notifyClient(client, method, notification) {
|
|
857
|
+
if (!this.clients.has(client) || client.socket.destroyed) return;
|
|
858
|
+
client.rpc.sendNotification(method, notification).catch(() => this.closeClient(client));
|
|
859
|
+
}
|
|
860
|
+
scheduleIdleExit() {
|
|
861
|
+
if (this.options.exitOnIdle === false || this.clients.size !== 0 || this.replicas.size !== 0 || this.idleTimer) return;
|
|
862
|
+
this.idleTimer = setTimeout(() => void this.stop(), IDLE_EXIT_MS);
|
|
863
|
+
this.idleTimer.unref();
|
|
864
|
+
}
|
|
865
|
+
cancelIdleExit() {
|
|
866
|
+
if (this.idleTimer) {
|
|
867
|
+
clearTimeout(this.idleTimer);
|
|
868
|
+
this.idleTimer = void 0;
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
async performStop() {
|
|
872
|
+
this.cancelIdleExit();
|
|
873
|
+
await this.replicas.stopAll();
|
|
874
|
+
await this.network.stopAll();
|
|
875
|
+
setLogSink(void 0);
|
|
876
|
+
for (const client of this.clients) {
|
|
877
|
+
client.rpc.dispose();
|
|
878
|
+
client.socket.destroy();
|
|
879
|
+
}
|
|
880
|
+
this.clients.clear();
|
|
881
|
+
if (this.server) await new Promise((resolveClose) => this.server.close(() => resolveClose()));
|
|
882
|
+
await rm(this.paths.metadataPath, { force: true });
|
|
883
|
+
if (process.platform !== "win32") await rm(this.paths.socketPath, { force: true });
|
|
884
|
+
}
|
|
953
885
|
};
|
|
954
|
-
|
|
955
886
|
// ../daemon/src/main.ts
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
887
|
+
const server = new LeafRelayDaemonServer();
|
|
888
|
+
const stop = () => {
|
|
889
|
+
process.off("SIGINT", stop);
|
|
890
|
+
process.off("SIGTERM", stop);
|
|
891
|
+
server.stop().finally(() => process.exit(0));
|
|
961
892
|
};
|
|
962
893
|
process.on("SIGINT", stop);
|
|
963
894
|
process.on("SIGTERM", stop);
|
|
964
895
|
process.on("uncaughtException", (error) => {
|
|
965
|
-
|
|
966
|
-
|
|
896
|
+
console.error(error);
|
|
897
|
+
server.stop().finally(() => process.exit(1));
|
|
967
898
|
});
|
|
968
899
|
process.on("unhandledRejection", (error) => {
|
|
969
|
-
|
|
970
|
-
|
|
900
|
+
console.error(error);
|
|
901
|
+
server.stop().finally(() => process.exit(1));
|
|
971
902
|
});
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
903
|
+
server.start().catch((error) => {
|
|
904
|
+
console.error(error);
|
|
905
|
+
process.exit(1);
|
|
975
906
|
});
|
|
907
|
+
export {};
|
|
908
|
+
|
|
976
909
|
//# sourceMappingURL=daemon.js.map
|