itwillsync 1.3.8 → 1.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/hub/daemon.js +149 -15
- package/dist/hub/daemon.js.map +1 -1
- package/dist/hub/dashboard/assets/{index-DgUZUPW_.js → index-DdOxsvuU.js} +1 -1
- package/dist/hub/dashboard/index.html +1 -1
- package/dist/index.js +192 -34
- package/dist/index.js.map +1 -1
- package/dist/web-client/assets/{index-CWuNNUhm.js → index-Bg1a3YQa.js} +17 -17
- package/dist/web-client/assets/index-CmAz03xC.css +1 -0
- package/dist/web-client/index.html +2 -2
- package/package.json +3 -3
- package/dist/web-client/assets/index-xsTGRLqf.css +0 -1
package/dist/hub/daemon.js
CHANGED
|
@@ -3649,9 +3649,9 @@ var require_websocket_server = __commonJS({
|
|
|
3649
3649
|
});
|
|
3650
3650
|
|
|
3651
3651
|
// src/daemon.ts
|
|
3652
|
-
import { writeFileSync, mkdirSync, unlinkSync } from "fs";
|
|
3653
|
-
import { homedir } from "os";
|
|
3654
|
-
import { join as
|
|
3652
|
+
import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, unlinkSync, existsSync as existsSync2, readdirSync, statSync } from "fs";
|
|
3653
|
+
import { homedir as homedir2 } from "os";
|
|
3654
|
+
import { join as join3, dirname as dirname2 } from "path";
|
|
3655
3655
|
import { fileURLToPath } from "url";
|
|
3656
3656
|
|
|
3657
3657
|
// src/auth.ts
|
|
@@ -3721,7 +3721,39 @@ import { randomBytes as randomBytes2 } from "crypto";
|
|
|
3721
3721
|
var SessionRegistry = class extends EventEmitter {
|
|
3722
3722
|
sessions = /* @__PURE__ */ new Map();
|
|
3723
3723
|
healthCheckInterval = null;
|
|
3724
|
+
maxSessions;
|
|
3725
|
+
store;
|
|
3726
|
+
constructor(options = {}) {
|
|
3727
|
+
super();
|
|
3728
|
+
this.maxSessions = options.maxSessions ?? 20;
|
|
3729
|
+
this.store = options.store ?? null;
|
|
3730
|
+
if (this.store) {
|
|
3731
|
+
const alive = this.store.loadAndMarkStale();
|
|
3732
|
+
for (const s of alive) {
|
|
3733
|
+
this.sessions.set(s.id, {
|
|
3734
|
+
id: s.id,
|
|
3735
|
+
name: s.name,
|
|
3736
|
+
port: s.port,
|
|
3737
|
+
token: s.token,
|
|
3738
|
+
agent: s.agent,
|
|
3739
|
+
cwd: s.cwd,
|
|
3740
|
+
pid: s.pid,
|
|
3741
|
+
connectedAt: s.connectedAt,
|
|
3742
|
+
lastSeen: s.lastSeen,
|
|
3743
|
+
status: s.status === "ended" ? "idle" : s.status
|
|
3744
|
+
});
|
|
3745
|
+
}
|
|
3746
|
+
}
|
|
3747
|
+
}
|
|
3748
|
+
persistSessions() {
|
|
3749
|
+
if (!this.store) return;
|
|
3750
|
+
const sessions = this.getAll().map((s) => ({ ...s }));
|
|
3751
|
+
this.store.save(sessions);
|
|
3752
|
+
}
|
|
3724
3753
|
register(registration) {
|
|
3754
|
+
if (this.sessions.size >= this.maxSessions) {
|
|
3755
|
+
throw new Error(`Maximum sessions reached (${this.maxSessions})`);
|
|
3756
|
+
}
|
|
3725
3757
|
const id = randomBytes2(8).toString("hex");
|
|
3726
3758
|
const now = Date.now();
|
|
3727
3759
|
const session = {
|
|
@@ -3733,12 +3765,14 @@ var SessionRegistry = class extends EventEmitter {
|
|
|
3733
3765
|
};
|
|
3734
3766
|
this.sessions.set(id, session);
|
|
3735
3767
|
this.emit("session-added", session);
|
|
3768
|
+
this.persistSessions();
|
|
3736
3769
|
return session;
|
|
3737
3770
|
}
|
|
3738
3771
|
unregister(id) {
|
|
3739
3772
|
const existed = this.sessions.delete(id);
|
|
3740
3773
|
if (existed) {
|
|
3741
3774
|
this.emit("session-removed", id);
|
|
3775
|
+
this.persistSessions();
|
|
3742
3776
|
}
|
|
3743
3777
|
return existed;
|
|
3744
3778
|
}
|
|
@@ -3772,6 +3806,7 @@ var SessionRegistry = class extends EventEmitter {
|
|
|
3772
3806
|
if (session) {
|
|
3773
3807
|
session.status = status;
|
|
3774
3808
|
this.emit("session-updated", session);
|
|
3809
|
+
this.persistSessions();
|
|
3775
3810
|
}
|
|
3776
3811
|
}
|
|
3777
3812
|
/**
|
|
@@ -3814,6 +3849,80 @@ var SessionRegistry = class extends EventEmitter {
|
|
|
3814
3849
|
}
|
|
3815
3850
|
};
|
|
3816
3851
|
|
|
3852
|
+
// src/session-store.ts
|
|
3853
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
3854
|
+
import { homedir } from "os";
|
|
3855
|
+
import { join, dirname } from "path";
|
|
3856
|
+
function getStorePath() {
|
|
3857
|
+
const dir = process.env.ITWILLSYNC_CONFIG_DIR || join(homedir(), ".itwillsync");
|
|
3858
|
+
return join(dir, "sessions.json");
|
|
3859
|
+
}
|
|
3860
|
+
var SessionStore = class {
|
|
3861
|
+
savePending = false;
|
|
3862
|
+
saveTimer = null;
|
|
3863
|
+
sessions = [];
|
|
3864
|
+
constructor() {
|
|
3865
|
+
this.sessions = this.readFromDisk();
|
|
3866
|
+
}
|
|
3867
|
+
/** Load sessions from disk and mark stale ones (dead processes) as ended. */
|
|
3868
|
+
loadAndMarkStale() {
|
|
3869
|
+
for (const session of this.sessions) {
|
|
3870
|
+
if (session.status === "ended") continue;
|
|
3871
|
+
try {
|
|
3872
|
+
process.kill(session.pid, 0);
|
|
3873
|
+
} catch {
|
|
3874
|
+
session.status = "ended";
|
|
3875
|
+
session.lastSeen = Date.now();
|
|
3876
|
+
}
|
|
3877
|
+
}
|
|
3878
|
+
const cutoff = Date.now() - 864e5;
|
|
3879
|
+
this.sessions = this.sessions.filter(
|
|
3880
|
+
(s) => s.status !== "ended" || s.lastSeen > cutoff
|
|
3881
|
+
);
|
|
3882
|
+
this.writeToDisk();
|
|
3883
|
+
return this.sessions.filter((s) => s.status !== "ended");
|
|
3884
|
+
}
|
|
3885
|
+
/** Save current sessions (debounced 500ms). */
|
|
3886
|
+
save(sessions) {
|
|
3887
|
+
this.sessions = sessions;
|
|
3888
|
+
if (this.saveTimer) return;
|
|
3889
|
+
this.saveTimer = setTimeout(() => {
|
|
3890
|
+
this.saveTimer = null;
|
|
3891
|
+
this.writeToDisk();
|
|
3892
|
+
}, 500);
|
|
3893
|
+
}
|
|
3894
|
+
/** Flush any pending save immediately. */
|
|
3895
|
+
flush() {
|
|
3896
|
+
if (this.saveTimer) {
|
|
3897
|
+
clearTimeout(this.saveTimer);
|
|
3898
|
+
this.saveTimer = null;
|
|
3899
|
+
}
|
|
3900
|
+
this.writeToDisk();
|
|
3901
|
+
}
|
|
3902
|
+
readFromDisk() {
|
|
3903
|
+
const path = getStorePath();
|
|
3904
|
+
if (!existsSync(path)) return [];
|
|
3905
|
+
try {
|
|
3906
|
+
const raw = readFileSync(path, "utf-8");
|
|
3907
|
+
const data = JSON.parse(raw);
|
|
3908
|
+
if (data.version !== 1 || !Array.isArray(data.sessions)) return [];
|
|
3909
|
+
return data.sessions;
|
|
3910
|
+
} catch {
|
|
3911
|
+
return [];
|
|
3912
|
+
}
|
|
3913
|
+
}
|
|
3914
|
+
writeToDisk() {
|
|
3915
|
+
const path = getStorePath();
|
|
3916
|
+
const dir = dirname(path);
|
|
3917
|
+
mkdirSync(dir, { recursive: true });
|
|
3918
|
+
const data = {
|
|
3919
|
+
version: 1,
|
|
3920
|
+
sessions: this.sessions
|
|
3921
|
+
};
|
|
3922
|
+
writeFileSync(path, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
3923
|
+
}
|
|
3924
|
+
};
|
|
3925
|
+
|
|
3817
3926
|
// src/internal-api.ts
|
|
3818
3927
|
import { createServer } from "http";
|
|
3819
3928
|
function createInternalApi(options) {
|
|
@@ -3858,7 +3967,14 @@ function createInternalApi(options) {
|
|
|
3858
3967
|
res.end(JSON.stringify({ error: "Missing required fields: name, port, token, agent, pid" }));
|
|
3859
3968
|
return;
|
|
3860
3969
|
}
|
|
3861
|
-
|
|
3970
|
+
let session;
|
|
3971
|
+
try {
|
|
3972
|
+
session = registry.register(data);
|
|
3973
|
+
} catch (err) {
|
|
3974
|
+
res.writeHead(503);
|
|
3975
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
3976
|
+
return;
|
|
3977
|
+
}
|
|
3862
3978
|
res.writeHead(201);
|
|
3863
3979
|
res.end(JSON.stringify({ session }));
|
|
3864
3980
|
return;
|
|
@@ -3985,7 +4101,7 @@ function readBody(req) {
|
|
|
3985
4101
|
// src/server.ts
|
|
3986
4102
|
import { createServer as createServer2 } from "http";
|
|
3987
4103
|
import { readFile } from "fs/promises";
|
|
3988
|
-
import { join, extname } from "path";
|
|
4104
|
+
import { join as join2, extname } from "path";
|
|
3989
4105
|
import { gzipSync } from "zlib";
|
|
3990
4106
|
|
|
3991
4107
|
// ../../node_modules/.pnpm/ws@8.19.0/node_modules/ws/wrapper.mjs
|
|
@@ -4058,7 +4174,7 @@ function createDashboardServer(options) {
|
|
|
4058
4174
|
await serveStaticFile(dashboardPath, pathname, req, res);
|
|
4059
4175
|
});
|
|
4060
4176
|
async function serveStaticFile(basePath, filePath, req, res) {
|
|
4061
|
-
const fullPath =
|
|
4177
|
+
const fullPath = join2(basePath, filePath);
|
|
4062
4178
|
const ext = extname(fullPath);
|
|
4063
4179
|
const contentType = MIME_TYPES[ext] || "application/octet-stream";
|
|
4064
4180
|
try {
|
|
@@ -4433,20 +4549,20 @@ var HUB_EXTERNAL_PORT = 7962;
|
|
|
4433
4549
|
var HUB_INTERNAL_PORT = 7963;
|
|
4434
4550
|
var AUTO_SHUTDOWN_DELAY_MS = 3e4;
|
|
4435
4551
|
function getHubDir() {
|
|
4436
|
-
return process.env.ITWILLSYNC_CONFIG_DIR ||
|
|
4552
|
+
return process.env.ITWILLSYNC_CONFIG_DIR || join3(homedir2(), ".itwillsync");
|
|
4437
4553
|
}
|
|
4438
4554
|
function getPidPath() {
|
|
4439
|
-
return
|
|
4555
|
+
return join3(getHubDir(), "hub.pid");
|
|
4440
4556
|
}
|
|
4441
4557
|
function getHubConfigPath() {
|
|
4442
|
-
return
|
|
4558
|
+
return join3(getHubDir(), "hub.json");
|
|
4443
4559
|
}
|
|
4444
4560
|
async function main() {
|
|
4445
4561
|
const hubDir = getHubDir();
|
|
4446
|
-
|
|
4562
|
+
mkdirSync2(hubDir, { recursive: true });
|
|
4447
4563
|
const masterToken = generateToken();
|
|
4448
4564
|
const startedAt = Date.now();
|
|
4449
|
-
|
|
4565
|
+
writeFileSync2(getPidPath(), String(process.pid), "utf-8");
|
|
4450
4566
|
const hubConfig = {
|
|
4451
4567
|
masterToken,
|
|
4452
4568
|
externalPort: HUB_EXTERNAL_PORT,
|
|
@@ -4454,11 +4570,28 @@ async function main() {
|
|
|
4454
4570
|
pid: process.pid,
|
|
4455
4571
|
startedAt
|
|
4456
4572
|
};
|
|
4457
|
-
|
|
4458
|
-
const
|
|
4573
|
+
writeFileSync2(getHubConfigPath(), JSON.stringify(hubConfig, null, 2) + "\n", "utf-8");
|
|
4574
|
+
const sessionStore = new SessionStore();
|
|
4575
|
+
const registry = new SessionRegistry({ store: sessionStore });
|
|
4459
4576
|
registry.startHealthChecks();
|
|
4460
|
-
const
|
|
4461
|
-
|
|
4577
|
+
const logsDir = join3(hubDir, "logs");
|
|
4578
|
+
if (existsSync2(logsDir)) {
|
|
4579
|
+
const retentionMs = 30 * 864e5;
|
|
4580
|
+
const cutoff = Date.now() - retentionMs;
|
|
4581
|
+
try {
|
|
4582
|
+
for (const file of readdirSync(logsDir)) {
|
|
4583
|
+
const filePath = join3(logsDir, file);
|
|
4584
|
+
try {
|
|
4585
|
+
const stat = statSync(filePath);
|
|
4586
|
+
if (stat.mtimeMs < cutoff) unlinkSync(filePath);
|
|
4587
|
+
} catch {
|
|
4588
|
+
}
|
|
4589
|
+
}
|
|
4590
|
+
} catch {
|
|
4591
|
+
}
|
|
4592
|
+
}
|
|
4593
|
+
const __dirname = dirname2(fileURLToPath(import.meta.url));
|
|
4594
|
+
const dashboardPath = join3(__dirname, "dashboard");
|
|
4462
4595
|
const internalApi = createInternalApi({
|
|
4463
4596
|
registry,
|
|
4464
4597
|
port: HUB_INTERNAL_PORT
|
|
@@ -4505,6 +4638,7 @@ async function main() {
|
|
|
4505
4638
|
function cleanup() {
|
|
4506
4639
|
previewCollector.close();
|
|
4507
4640
|
registry.stopHealthChecks();
|
|
4641
|
+
sessionStore.flush();
|
|
4508
4642
|
registry.clear();
|
|
4509
4643
|
internalApi.close();
|
|
4510
4644
|
dashboardServer.close();
|