itwillsync 1.3.7 → 1.5.1

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.
@@ -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 join2, dirname } from "path";
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
- const session = registry.register(data);
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 = join(basePath, filePath);
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 || join2(homedir(), ".itwillsync");
4552
+ return process.env.ITWILLSYNC_CONFIG_DIR || join3(homedir2(), ".itwillsync");
4437
4553
  }
4438
4554
  function getPidPath() {
4439
- return join2(getHubDir(), "hub.pid");
4555
+ return join3(getHubDir(), "hub.pid");
4440
4556
  }
4441
4557
  function getHubConfigPath() {
4442
- return join2(getHubDir(), "hub.json");
4558
+ return join3(getHubDir(), "hub.json");
4443
4559
  }
4444
4560
  async function main() {
4445
4561
  const hubDir = getHubDir();
4446
- mkdirSync(hubDir, { recursive: true });
4562
+ mkdirSync2(hubDir, { recursive: true });
4447
4563
  const masterToken = generateToken();
4448
4564
  const startedAt = Date.now();
4449
- writeFileSync(getPidPath(), String(process.pid), "utf-8");
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
- writeFileSync(getHubConfigPath(), JSON.stringify(hubConfig, null, 2) + "\n", "utf-8");
4458
- const registry = new SessionRegistry();
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 __dirname = dirname(fileURLToPath(import.meta.url));
4461
- const dashboardPath = join2(__dirname, "dashboard");
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();