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
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const a of document.querySelectorAll('link[rel="modulepreload"]'))o(a);new MutationObserver(a=>{for(const s of a)if(s.type==="childList")for(const i of s.addedNodes)i.tagName==="LINK"&&i.rel==="modulepreload"&&o(i)}).observe(document,{childList:!0,subtree:!0});function n(a){const s={};return a.integrity&&(s.integrity=a.integrity),a.referrerPolicy&&(s.referrerPolicy=a.referrerPolicy),a.crossOrigin==="use-credentials"?s.credentials="include":a.crossOrigin==="anonymous"?s.credentials="omit":s.credentials="same-origin",s}function o(a){if(a.ep)return;a.ep=!0;const s=n(a);fetch(a.href,s)}})();function B(e){const t=Math.floor(e/1e3);if(t<60)return`${t}s`;const n=Math.floor(t/60);if(n<60)return`${n}m`;const o=Math.floor(n/60),a=n%60;return`${o}h ${a}m`}function z(e){const t=e.match(/^\/(?:Users|home)\/[^/]+/)?.[0];return t?"~"+e.slice(t.length):e}function X(e){return e<1024?`${e} KB`:`${(e/1024).toFixed(1)} MB`}function H(e,t,n){const o=document.createElement("div");o.className=`session-card${e.status==="attention"?" attention":""}`,o.dataset.sessionId=e.id;const a=B(Date.now()-e.connectedAt),s=z(e.cwd),i=document.createElement("div");i.className="card-header";const c=document.createElement("div");c.className="card-agent";const p=document.createElement("div");p.className=`agent-dot ${e.status}`;const P=document.createElement("span");P.className="agent-name",P.textContent=e.name||e.agent,c.appendChild(p),c.appendChild(P);const k=document.createElement("span");k.className="card-uptime",k.textContent=a,i.appendChild(c),i.appendChild(k);const I=document.createElement("div");I.className="card-cwd",I.textContent=s;const h=document.createElement("div");h.className="card-preview";const $=document.createElement("pre");$.className="card-preview-text",$.textContent="Waiting for output...",h.appendChild($),h.addEventListener("click",l=>{l.stopPropagation(),n.onOpen(e)});const g=document.createElement("div");g.className="card-status";const E=document.createElement("span");E.className=`status-badge ${e.status}`,E.textContent=e.status,e.status==="attention"&&(E.style.display="none");const v=document.createElement("span");v.className="attention-badge",v.textContent="Needs your attention",e.status!=="attention"&&(v.style.display="none"),g.appendChild(E),g.appendChild(v);const f=document.createElement("div");f.className="card-actions";const C=document.createElement("button");C.className="action-btn stop",C.textContent="Stop",C.addEventListener("click",l=>{l.stopPropagation(),Q(o,e.id,n.onStop)});const w=document.createElement("button");w.className="action-btn rename",w.textContent="Rename",w.addEventListener("click",l=>{l.stopPropagation(),Z(o,e.id,n.onRename)});const N=document.createElement("button");N.className="action-btn info",N.textContent="Info",N.addEventListener("click",l=>{l.stopPropagation(),n.onInfo(e.id)});const b=document.createElement("button");b.className="action-btn open",b.textContent="Open",b.addEventListener("click",l=>{l.stopPropagation(),n.onOpen(e)}),f.appendChild(b),f.appendChild(w),f.appendChild(N),f.appendChild(C);const R=document.createElement("div");return R.className="card-metadata hidden",o.addEventListener("click",()=>{n.onOpen(e)}),o.appendChild(i),o.appendChild(I),o.appendChild(h),o.appendChild(g),o.appendChild(f),o.appendChild(R),o}function Q(e,t,n){e.querySelector(".confirm-overlay")?.remove();const o=document.createElement("div");o.className="confirm-overlay";const a=document.createElement("span");a.className="confirm-msg",a.textContent="Stop this session?";const s=document.createElement("button");s.className="confirm-btn yes",s.textContent="Yes",s.addEventListener("click",c=>{c.stopPropagation(),n(t),o.remove()});const i=document.createElement("button");i.className="confirm-btn no",i.textContent="No",i.addEventListener("click",c=>{c.stopPropagation(),o.remove()}),o.addEventListener("click",c=>c.stopPropagation()),o.appendChild(a),o.appendChild(s),o.appendChild(i),e.appendChild(o)}function Z(e,t,n){const o=e.querySelector(".agent-name");if(!o)return;const a=o.textContent||"",s=document.createElement("input");s.className="rename-input",s.type="text",s.value=a;const i=()=>{const c=s.value.trim();c&&c!==a&&n(t,c),o.textContent=c||a,o.style.display="",s.remove()};s.addEventListener("keydown",c=>{c.stopPropagation(),c.key==="Enter"?i():c.key==="Escape"&&(o.textContent=a,o.style.display="",s.remove())}),s.addEventListener("blur",i),s.addEventListener("click",c=>c.stopPropagation()),o.style.display="none",o.parentElement?.insertBefore(s,o.nextSibling),s.focus(),s.select()}function G(e,t){const n=e.querySelector(".agent-dot"),o=e.querySelector(".status-badge"),a=e.querySelector(".card-uptime"),s=e.querySelector(".agent-name");n&&(n.className=`agent-dot ${t.status}`),o&&(o.className=`status-badge ${t.status}`,o.textContent=t.status),a&&(a.textContent=B(Date.now()-t.connectedAt)),s&&!s.style.display&&(s.textContent=t.name||t.agent);const i=t.status==="attention";o&&(o.style.display=i?"none":"");const c=e.querySelector(".attention-badge");c&&(c.style.display=i?"":"none"),t.status==="attention"?e.classList.add("attention"):e.classList.remove("attention")}function ee(e,t){const n=e.querySelector(".card-preview-text");n&&(t.length>0?(n.textContent=t.join(`
|
|
2
|
-
`),n.classList.remove("empty")):(n.textContent="Waiting for output...",n.classList.add("empty")))}function te(e,t){const n=e.querySelector(".card-metadata");if(!n)return;for(;n.firstChild;)n.removeChild(n.firstChild);const o=[["PID",String(t.pid)],["Agent",t.agent],["Port",String(t.port)],["Directory",t.cwd],["Memory",X(t.memoryKB)],["Uptime",B(t.uptimeMs)]];for(const[a,s]of o){const i=document.createElement("div");i.className="meta-row";const c=document.createElement("span");c.className="meta-label",c.textContent=a;const p=document.createElement("span");p.className="meta-value",p.textContent=s,i.appendChild(c),i.appendChild(p),n.appendChild(i)}n.classList.toggle("hidden")}let r=null,q=!1;function ne(){if(!q){q=!0;try{r=new AudioContext,r.state==="suspended"&&r.resume()}catch{}}}function U(){if(!r)return;r.state==="suspended"&&r.resume();const e=r.currentTime,t=r.createOscillator(),n=r.createGain();t.frequency.value=587.33,t.type="sine",n.gain.setValueAtTime(.3,e),n.gain.exponentialRampToValueAtTime(.01,e+.3),t.connect(n),n.connect(r.destination),t.start(e),t.stop(e+.3);const o=r.createOscillator(),a=r.createGain();o.frequency.value=880,o.type="sine",a.gain.setValueAtTime(.3,e+.15),a.gain.exponentialRampToValueAtTime(.01,e+.45),o.connect(a),a.connect(r.destination),o.start(e+.15),o.stop(e+.45)}const D=120*1e3,u=new Map;function
|
|
2
|
+
`),n.classList.remove("empty")):(n.textContent="Waiting for output...",n.classList.add("empty")))}function te(e,t){const n=e.querySelector(".card-metadata");if(!n)return;for(;n.firstChild;)n.removeChild(n.firstChild);const o=[["PID",String(t.pid)],["Agent",t.agent],["Port",String(t.port)],["Directory",t.cwd],["Memory",X(t.memoryKB)],["Uptime",B(t.uptimeMs)]];for(const[a,s]of o){const i=document.createElement("div");i.className="meta-row";const c=document.createElement("span");c.className="meta-label",c.textContent=a;const p=document.createElement("span");p.className="meta-value",p.textContent=s,i.appendChild(c),i.appendChild(p),n.appendChild(i)}n.classList.toggle("hidden")}let r=null,q=!1;function ne(){if(!q){q=!0;try{r=new AudioContext,r.state==="suspended"&&r.resume()}catch{}}}function U(){if(!r)return;r.state==="suspended"&&r.resume();const e=r.currentTime,t=r.createOscillator(),n=r.createGain();t.frequency.value=587.33,t.type="sine",n.gain.setValueAtTime(.3,e),n.gain.exponentialRampToValueAtTime(.01,e+.3),t.connect(n),n.connect(r.destination),t.start(e),t.stop(e+.3);const o=r.createOscillator(),a=r.createGain();o.frequency.value=880,o.type="sine",a.gain.setValueAtTime(.3,e+.15),a.gain.exponentialRampToValueAtTime(.01,e+.45),o.connect(a),a.connect(r.destination),o.start(e+.15),o.stop(e+.45)}const D=120*1e3,u=new Map;function V(e){if(u.has(e))return;U();const t=setTimeout(function n(){U();const o=u.get(e);o&&(o.timerId=setTimeout(n,D))},D);u.set(e,{timerId:t})}function A(e){const t=u.get(e);t&&(clearTimeout(t.timerId),u.delete(e))}function W(){for(const e of u.values())clearTimeout(e.timerId);u.clear()}const oe=new URLSearchParams(window.location.search),J=oe.get("token");if(!J)throw document.body.textContent="Missing authentication token.",new Error("No token in URL");const ae=window.location.protocol==="https:"?"wss:":"ws:",se=`${ae}//${window.location.host}?token=${J}`,Y=window.location.hostname,y=new Map,m=new Map,ce=document.getElementById("session-list"),O=document.getElementById("empty-state"),ie=document.getElementById("session-count"),_=document.getElementById("status-dot");let M=null;function x(){ne(),document.removeEventListener("click",x),document.removeEventListener("touchstart",x)}document.addEventListener("click",x);document.addEventListener("touchstart",x);function S(e){d&&d.readyState===WebSocket.OPEN&&d.send(JSON.stringify(e))}const re={onOpen(e){const t=y.get(e.id)||e;t.status==="attention"&&(S({type:"clear-attention",sessionId:t.id}),A(t.id));const n=window.location.href,o=`http://${Y}:${t.port}?token=${t.token}&hub=${encodeURIComponent(n)}`;window.open(o,`session-${t.id}`)},onStop(e){S({type:"stop-session",sessionId:e})},onRename(e,t){S({type:"rename-session",sessionId:e,name:t})},onInfo(e){S({type:"get-metadata",sessionId:e})}};function j(){const e=y.size;ie.textContent=`${e} session${e!==1?"s":""}`,e===0?O.style.display="flex":O.style.display="none"}function F(e){y.set(e.id,e);const t=H(e,Y,re);m.set(e.id,t),ce.insertBefore(t,O),j()}function K(e){y.delete(e);const t=m.get(e);t&&(t.remove(),m.delete(e)),j()}function de(e){y.set(e.id,e);const t=m.get(e.id);t&&G(t,e)}function le(){for(const[e,t]of y){const n=m.get(e);n&&G(n,t)}}let d=null,L=0;const me=1e4;function T(){d=new WebSocket(se),d.onopen=()=>{_.className="connected",L=0,M&&clearInterval(M),M=setInterval(le,1e4)},d.onmessage=e=>{try{const t=JSON.parse(e.data);switch(t.type){case"sessions":{W();for(const n of m.keys())K(n);for(const n of t.sessions)F(n),n.status==="attention"&&V(n.id);break}case"session-added":{F(t.session);break}case"session-removed":{const n=t.sessionId;A(n),K(n);break}case"session-updated":{const n=t.session;de(n),n.status==="attention"?V(n.id):A(n.id);break}case"preview":{const n=m.get(t.sessionId);n&&ee(n,t.lines);break}case"metadata":{const n=m.get(t.sessionId);n&&te(n,t.metadata);break}case"operation-error":{console.warn(`Operation "${t.operation}" failed for session ${t.sessionId}: ${t.error}`);break}}}catch{}},d.onclose=()=>{_.className="reconnecting",W(),ue()},d.onerror=()=>{d?.close()}}function ue(){const e=Math.min(1e3*Math.pow(1.5,L),me);L++,setTimeout(T,e)}document.addEventListener("visibilitychange",()=>{document.visibilityState==="visible"&&d?.readyState!==WebSocket.OPEN&&(L=0,T())});T();
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
|
8
8
|
<meta name="theme-color" content="#1a1a2e" />
|
|
9
9
|
<title>itwillsync Dashboard</title>
|
|
10
|
-
<script type="module" crossorigin src="/assets/index-
|
|
10
|
+
<script type="module" crossorigin src="/assets/index-DdOxsvuU.js"></script>
|
|
11
11
|
<link rel="stylesheet" crossorigin href="/assets/index-Erqx_a0N.css">
|
|
12
12
|
</head>
|
|
13
13
|
<body>
|
package/dist/index.js
CHANGED
|
@@ -3732,9 +3732,11 @@ var PtyManager = class {
|
|
|
3732
3732
|
*/
|
|
3733
3733
|
resize(cols, rows) {
|
|
3734
3734
|
try {
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
this.
|
|
3735
|
+
const clampedCols = Math.max(1, Math.min(500, Math.floor(cols)));
|
|
3736
|
+
const clampedRows = Math.max(1, Math.min(200, Math.floor(rows)));
|
|
3737
|
+
this.ptyProcess.resize(clampedCols, clampedRows);
|
|
3738
|
+
this._cols = clampedCols;
|
|
3739
|
+
this._rows = clampedRows;
|
|
3738
3740
|
} catch {
|
|
3739
3741
|
}
|
|
3740
3742
|
}
|
|
@@ -3833,17 +3835,40 @@ async function getTailscaleStatus() {
|
|
|
3833
3835
|
}
|
|
3834
3836
|
|
|
3835
3837
|
// src/network.ts
|
|
3838
|
+
var VIRTUAL_INTERFACE_PREFIXES = [
|
|
3839
|
+
"utun",
|
|
3840
|
+
"tun",
|
|
3841
|
+
"tap",
|
|
3842
|
+
"wg",
|
|
3843
|
+
// VPN/tunnel
|
|
3844
|
+
"tailscale",
|
|
3845
|
+
// Tailscale
|
|
3846
|
+
"docker",
|
|
3847
|
+
"br-",
|
|
3848
|
+
"veth",
|
|
3849
|
+
// Docker
|
|
3850
|
+
"virbr",
|
|
3851
|
+
"vboxnet",
|
|
3852
|
+
"vmnet"
|
|
3853
|
+
// VM
|
|
3854
|
+
];
|
|
3855
|
+
function isVirtualInterface(name) {
|
|
3856
|
+
return VIRTUAL_INTERFACE_PREFIXES.some((prefix) => name.startsWith(prefix));
|
|
3857
|
+
}
|
|
3836
3858
|
function getLocalIP() {
|
|
3837
3859
|
const interfaces = networkInterfaces();
|
|
3838
|
-
|
|
3860
|
+
let fallback = null;
|
|
3861
|
+
for (const [name, addresses] of Object.entries(interfaces)) {
|
|
3839
3862
|
if (!addresses) continue;
|
|
3840
3863
|
for (const addr of addresses) {
|
|
3841
|
-
if (addr.family
|
|
3864
|
+
if (addr.family !== "IPv4" || addr.internal) continue;
|
|
3865
|
+
if (!isVirtualInterface(name)) {
|
|
3842
3866
|
return addr.address;
|
|
3843
3867
|
}
|
|
3868
|
+
fallback ??= addr.address;
|
|
3844
3869
|
}
|
|
3845
3870
|
}
|
|
3846
|
-
return "127.0.0.1";
|
|
3871
|
+
return fallback ?? "127.0.0.1";
|
|
3847
3872
|
}
|
|
3848
3873
|
async function resolveSessionIP(mode, isLocalhost) {
|
|
3849
3874
|
if (isLocalhost) return "127.0.0.1";
|
|
@@ -3937,12 +3962,25 @@ async function serveStaticFile(webClientPath, filePath, req, res) {
|
|
|
3937
3962
|
}
|
|
3938
3963
|
}
|
|
3939
3964
|
var PING_INTERVAL_MS = 3e4;
|
|
3940
|
-
var
|
|
3965
|
+
var DEFAULT_SCROLLBACK_SIZE = 10485760;
|
|
3966
|
+
var DEFAULT_CLIENT_BUFFER_LIMIT = 262144;
|
|
3941
3967
|
function createSyncServer(options) {
|
|
3942
|
-
const {
|
|
3968
|
+
const {
|
|
3969
|
+
ptyManager,
|
|
3970
|
+
token,
|
|
3971
|
+
webClientPath,
|
|
3972
|
+
host,
|
|
3973
|
+
port,
|
|
3974
|
+
resizePolicy = "last-writer-wins",
|
|
3975
|
+
scrollbackBufferSize = DEFAULT_SCROLLBACK_SIZE,
|
|
3976
|
+
clientBufferLimit = DEFAULT_CLIENT_BUFFER_LIMIT,
|
|
3977
|
+
logger
|
|
3978
|
+
} = options;
|
|
3943
3979
|
const clients = /* @__PURE__ */ new Set();
|
|
3944
3980
|
const aliveMap = /* @__PURE__ */ new WeakMap();
|
|
3981
|
+
const dropCounters = /* @__PURE__ */ new WeakMap();
|
|
3945
3982
|
let scrollbackBuffer = "";
|
|
3983
|
+
let scrollbackBytes = 0;
|
|
3946
3984
|
let seq = 0;
|
|
3947
3985
|
const httpServer = createServer2(async (req, res) => {
|
|
3948
3986
|
const url = new URL(req.url || "/", `http://${req.headers.host}`);
|
|
@@ -3979,9 +4017,12 @@ function createSyncServer(options) {
|
|
|
3979
4017
|
wssServer.on("connection", (ws) => {
|
|
3980
4018
|
clients.add(ws);
|
|
3981
4019
|
aliveMap.set(ws, true);
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
4020
|
+
let syncReceived = false;
|
|
4021
|
+
const fallbackTimer = setTimeout(() => {
|
|
4022
|
+
if (!syncReceived && scrollbackBuffer.length > 0) {
|
|
4023
|
+
ws.send(JSON.stringify({ type: "data", data: scrollbackBuffer, seq }));
|
|
4024
|
+
}
|
|
4025
|
+
}, 150);
|
|
3985
4026
|
ws.send(JSON.stringify({ type: "resize", cols: ptyManager.cols, rows: ptyManager.rows }));
|
|
3986
4027
|
ws.on("pong", () => {
|
|
3987
4028
|
aliveMap.set(ws, true);
|
|
@@ -3992,10 +4033,32 @@ function createSyncServer(options) {
|
|
|
3992
4033
|
if (message.type === "input" && typeof message.data === "string") {
|
|
3993
4034
|
ptyManager.write(message.data);
|
|
3994
4035
|
} else if (message.type === "resize" && typeof message.cols === "number" && typeof message.rows === "number") {
|
|
3995
|
-
if (
|
|
4036
|
+
if (resizePolicy === "last-writer-wins") {
|
|
3996
4037
|
ptyManager.resize(message.cols, message.rows);
|
|
4038
|
+
const resizeMsg = JSON.stringify({ type: "resize", cols: ptyManager.cols, rows: ptyManager.rows });
|
|
4039
|
+
for (const c of clients) {
|
|
4040
|
+
if (c.readyState === c.OPEN) {
|
|
4041
|
+
c.send(resizeMsg);
|
|
4042
|
+
}
|
|
4043
|
+
}
|
|
4044
|
+
}
|
|
4045
|
+
} else if (message.type === "sync" && typeof message.lastSeq === "number") {
|
|
4046
|
+
syncReceived = true;
|
|
4047
|
+
clearTimeout(fallbackTimer);
|
|
4048
|
+
if (message.lastSeq === -1 || message.lastSeq < seq - scrollbackBuffer.length) {
|
|
4049
|
+
if (scrollbackBuffer.length > 0) {
|
|
4050
|
+
ws.send(JSON.stringify({ type: "data", data: scrollbackBuffer, seq }));
|
|
4051
|
+
}
|
|
4052
|
+
} else {
|
|
4053
|
+
const missed = seq - message.lastSeq;
|
|
4054
|
+
if (missed > 0 && scrollbackBuffer.length > 0) {
|
|
4055
|
+
const delta = missed <= scrollbackBuffer.length ? scrollbackBuffer.slice(-missed) : scrollbackBuffer;
|
|
4056
|
+
ws.send(JSON.stringify({ type: "data", data: delta, seq }));
|
|
4057
|
+
}
|
|
3997
4058
|
}
|
|
3998
4059
|
} else if (message.type === "resume" && typeof message.lastSeq === "number") {
|
|
4060
|
+
syncReceived = true;
|
|
4061
|
+
clearTimeout(fallbackTimer);
|
|
3999
4062
|
const missed = seq - message.lastSeq;
|
|
4000
4063
|
if (missed > 0 && scrollbackBuffer.length > 0) {
|
|
4001
4064
|
const delta = missed <= scrollbackBuffer.length ? scrollbackBuffer.slice(-missed) : scrollbackBuffer;
|
|
@@ -4013,16 +4076,31 @@ function createSyncServer(options) {
|
|
|
4013
4076
|
});
|
|
4014
4077
|
});
|
|
4015
4078
|
ptyManager.onData((data) => {
|
|
4079
|
+
if (logger) logger.write(data);
|
|
4016
4080
|
seq += data.length;
|
|
4017
4081
|
scrollbackBuffer += data;
|
|
4018
|
-
|
|
4019
|
-
|
|
4082
|
+
scrollbackBytes += Buffer.byteLength(data, "utf-8");
|
|
4083
|
+
if (scrollbackBytes > scrollbackBufferSize) {
|
|
4084
|
+
const target = Math.floor(scrollbackBufferSize * 0.5);
|
|
4085
|
+
const excessBytes = scrollbackBytes - target;
|
|
4086
|
+
const avgBytesPerChar = scrollbackBytes / scrollbackBuffer.length;
|
|
4087
|
+
const trimChars = Math.min(Math.ceil(excessBytes / avgBytesPerChar), scrollbackBuffer.length);
|
|
4088
|
+
const trimmed = scrollbackBuffer.slice(0, trimChars);
|
|
4089
|
+
scrollbackBuffer = scrollbackBuffer.slice(trimChars);
|
|
4090
|
+
scrollbackBytes -= Buffer.byteLength(trimmed, "utf-8");
|
|
4020
4091
|
}
|
|
4021
4092
|
const msg = JSON.stringify({ type: "data", data, seq });
|
|
4022
4093
|
for (const client of clients) {
|
|
4023
|
-
if (client.readyState
|
|
4024
|
-
|
|
4094
|
+
if (client.readyState !== client.OPEN) continue;
|
|
4095
|
+
if (client.bufferedAmount > clientBufferLimit) {
|
|
4096
|
+
const count = (dropCounters.get(client) || 0) + 1;
|
|
4097
|
+
dropCounters.set(client, count);
|
|
4098
|
+
if (count === 100 || count > 100 && count % 1e3 === 0) {
|
|
4099
|
+
console.warn(` Warning: Dropped ${count} messages for slow client`);
|
|
4100
|
+
}
|
|
4101
|
+
continue;
|
|
4025
4102
|
}
|
|
4103
|
+
client.send(msg);
|
|
4026
4104
|
}
|
|
4027
4105
|
});
|
|
4028
4106
|
httpServer.listen(port, host);
|
|
@@ -4038,8 +4116,9 @@ function createSyncServer(options) {
|
|
|
4038
4116
|
wssServer.close();
|
|
4039
4117
|
httpServer.close();
|
|
4040
4118
|
},
|
|
4041
|
-
|
|
4042
|
-
|
|
4119
|
+
resizeFromLocal(cols, rows) {
|
|
4120
|
+
ptyManager.resize(cols, rows);
|
|
4121
|
+
const msg = JSON.stringify({ type: "resize", cols: ptyManager.cols, rows: ptyManager.rows });
|
|
4043
4122
|
for (const client of clients) {
|
|
4044
4123
|
if (client.readyState === client.OPEN) {
|
|
4045
4124
|
client.send(msg);
|
|
@@ -4072,7 +4151,17 @@ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
|
4072
4151
|
import { homedir } from "os";
|
|
4073
4152
|
import { join as join3 } from "path";
|
|
4074
4153
|
var DEFAULT_CONFIG = {
|
|
4075
|
-
networkingMode: "local"
|
|
4154
|
+
networkingMode: "local",
|
|
4155
|
+
scrollbackBufferSize: 10485760,
|
|
4156
|
+
// 10MB
|
|
4157
|
+
maxSessions: 20,
|
|
4158
|
+
idleTimeoutMs: 864e5,
|
|
4159
|
+
// 24 hours
|
|
4160
|
+
logRetentionDays: 30,
|
|
4161
|
+
clientBufferLimit: 262144,
|
|
4162
|
+
// 256KB
|
|
4163
|
+
maxTerminalCols: 500,
|
|
4164
|
+
maxTerminalRows: 200
|
|
4076
4165
|
};
|
|
4077
4166
|
function getConfigDir() {
|
|
4078
4167
|
return process.env.ITWILLSYNC_CONFIG_DIR || join3(homedir(), ".itwillsync");
|
|
@@ -4102,6 +4191,65 @@ function saveConfig(config) {
|
|
|
4102
4191
|
);
|
|
4103
4192
|
}
|
|
4104
4193
|
|
|
4194
|
+
// src/session-logger.ts
|
|
4195
|
+
import { createWriteStream, mkdirSync as mkdirSync2, unlinkSync } from "fs";
|
|
4196
|
+
import { createGzip } from "zlib";
|
|
4197
|
+
import { pipeline } from "stream/promises";
|
|
4198
|
+
import { createReadStream } from "fs";
|
|
4199
|
+
import { join as join4 } from "path";
|
|
4200
|
+
var FLUSH_INTERVAL_MS = 100;
|
|
4201
|
+
var BUFFER_SIZE = 4096;
|
|
4202
|
+
function getLogsDir() {
|
|
4203
|
+
return join4(getConfigDir(), "logs");
|
|
4204
|
+
}
|
|
4205
|
+
var SessionLogger = class {
|
|
4206
|
+
buffer = "";
|
|
4207
|
+
stream;
|
|
4208
|
+
logPath;
|
|
4209
|
+
flushTimer;
|
|
4210
|
+
closed = false;
|
|
4211
|
+
constructor(sessionId) {
|
|
4212
|
+
const logsDir = getLogsDir();
|
|
4213
|
+
mkdirSync2(logsDir, { recursive: true });
|
|
4214
|
+
this.logPath = join4(logsDir, `${sessionId}.log`);
|
|
4215
|
+
this.stream = createWriteStream(this.logPath, { flags: "a" });
|
|
4216
|
+
this.flushTimer = setInterval(() => this.flush(), FLUSH_INTERVAL_MS);
|
|
4217
|
+
}
|
|
4218
|
+
write(data) {
|
|
4219
|
+
if (this.closed) return;
|
|
4220
|
+
this.buffer += data;
|
|
4221
|
+
if (this.buffer.length >= BUFFER_SIZE) {
|
|
4222
|
+
this.flush();
|
|
4223
|
+
}
|
|
4224
|
+
}
|
|
4225
|
+
flush() {
|
|
4226
|
+
if (this.buffer.length === 0 || this.closed) return;
|
|
4227
|
+
this.stream.write(this.buffer);
|
|
4228
|
+
this.buffer = "";
|
|
4229
|
+
}
|
|
4230
|
+
async close() {
|
|
4231
|
+
if (this.closed) return;
|
|
4232
|
+
this.closed = true;
|
|
4233
|
+
clearInterval(this.flushTimer);
|
|
4234
|
+
this.flush();
|
|
4235
|
+
await new Promise((resolve) => {
|
|
4236
|
+
this.stream.end(() => resolve());
|
|
4237
|
+
});
|
|
4238
|
+
try {
|
|
4239
|
+
const gzPath = this.logPath + ".gz";
|
|
4240
|
+
const source = createReadStream(this.logPath);
|
|
4241
|
+
const gzip = createGzip();
|
|
4242
|
+
const dest = createWriteStream(gzPath);
|
|
4243
|
+
await pipeline(source, gzip, dest);
|
|
4244
|
+
try {
|
|
4245
|
+
unlinkSync(this.logPath);
|
|
4246
|
+
} catch {
|
|
4247
|
+
}
|
|
4248
|
+
} catch {
|
|
4249
|
+
}
|
|
4250
|
+
}
|
|
4251
|
+
};
|
|
4252
|
+
|
|
4105
4253
|
// src/wizard.ts
|
|
4106
4254
|
import * as p from "@clack/prompts";
|
|
4107
4255
|
async function runSetupWizard() {
|
|
@@ -4173,23 +4321,23 @@ async function runSetupWizard() {
|
|
|
4173
4321
|
// src/cli-options.ts
|
|
4174
4322
|
import { readFileSync as readFileSync3 } from "fs";
|
|
4175
4323
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
4176
|
-
import { dirname as dirname3, join as
|
|
4324
|
+
import { dirname as dirname3, join as join6 } from "path";
|
|
4177
4325
|
|
|
4178
4326
|
// src/hub-client.ts
|
|
4179
4327
|
import { spawn as spawn2 } from "child_process";
|
|
4180
|
-
import { readFileSync as readFileSync2, existsSync as existsSync2, unlinkSync } from "fs";
|
|
4328
|
+
import { readFileSync as readFileSync2, existsSync as existsSync2, unlinkSync as unlinkSync2 } from "fs";
|
|
4181
4329
|
import { homedir as homedir2 } from "os";
|
|
4182
|
-
import { join as
|
|
4330
|
+
import { join as join5, dirname as dirname2 } from "path";
|
|
4183
4331
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4184
4332
|
import { request } from "http";
|
|
4185
4333
|
var HUB_INTERNAL_PORT = 7963;
|
|
4186
4334
|
var HUB_EXTERNAL_PORT = 7962;
|
|
4187
4335
|
var SESSION_PORT_START = 7964;
|
|
4188
4336
|
function getHubDir() {
|
|
4189
|
-
return process.env.ITWILLSYNC_CONFIG_DIR ||
|
|
4337
|
+
return process.env.ITWILLSYNC_CONFIG_DIR || join5(homedir2(), ".itwillsync");
|
|
4190
4338
|
}
|
|
4191
4339
|
function getHubConfigPath() {
|
|
4192
|
-
return
|
|
4340
|
+
return join5(getHubDir(), "hub.json");
|
|
4193
4341
|
}
|
|
4194
4342
|
async function discoverHub() {
|
|
4195
4343
|
return new Promise((resolve) => {
|
|
@@ -4272,18 +4420,18 @@ async function killStaleHub() {
|
|
|
4272
4420
|
}
|
|
4273
4421
|
const hubDir = getHubDir();
|
|
4274
4422
|
try {
|
|
4275
|
-
|
|
4423
|
+
unlinkSync2(join5(hubDir, "hub.json"));
|
|
4276
4424
|
} catch {
|
|
4277
4425
|
}
|
|
4278
4426
|
try {
|
|
4279
|
-
|
|
4427
|
+
unlinkSync2(join5(hubDir, "hub.pid"));
|
|
4280
4428
|
} catch {
|
|
4281
4429
|
}
|
|
4282
4430
|
return true;
|
|
4283
4431
|
}
|
|
4284
4432
|
async function spawnHub() {
|
|
4285
4433
|
const __dirname2 = dirname2(fileURLToPath2(import.meta.url));
|
|
4286
|
-
const hubPath =
|
|
4434
|
+
const hubPath = join5(__dirname2, "hub", "daemon.js");
|
|
4287
4435
|
return new Promise((resolve, reject) => {
|
|
4288
4436
|
const child = spawn2("node", [hubPath], {
|
|
4289
4437
|
detached: true,
|
|
@@ -4478,7 +4626,7 @@ async function sendHeartbeat(sessionId) {
|
|
|
4478
4626
|
var __filename = fileURLToPath3(import.meta.url);
|
|
4479
4627
|
var __dirname = dirname3(__filename);
|
|
4480
4628
|
var { version } = JSON.parse(
|
|
4481
|
-
readFileSync3(
|
|
4629
|
+
readFileSync3(join6(__dirname, "..", "package.json"), "utf-8")
|
|
4482
4630
|
);
|
|
4483
4631
|
var DEFAULT_PORT = SESSION_PORT_START;
|
|
4484
4632
|
function parseArgs(argv) {
|
|
@@ -4594,7 +4742,7 @@ Hub Management:
|
|
|
4594
4742
|
|
|
4595
4743
|
// src/index.ts
|
|
4596
4744
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
4597
|
-
import { join as
|
|
4745
|
+
import { join as join7, dirname as dirname4 } from "path";
|
|
4598
4746
|
import { spawn as spawn3 } from "child_process";
|
|
4599
4747
|
function preventSleep() {
|
|
4600
4748
|
try {
|
|
@@ -4729,13 +4877,13 @@ async function main() {
|
|
|
4729
4877
|
printHelp();
|
|
4730
4878
|
process.exit(1);
|
|
4731
4879
|
}
|
|
4880
|
+
const config = loadConfig();
|
|
4732
4881
|
let networkingMode = "local";
|
|
4733
4882
|
if (options.tailscale) {
|
|
4734
4883
|
networkingMode = "tailscale";
|
|
4735
4884
|
} else if (options.local) {
|
|
4736
4885
|
networkingMode = "local";
|
|
4737
4886
|
} else {
|
|
4738
|
-
const config = loadConfig();
|
|
4739
4887
|
networkingMode = config.networkingMode;
|
|
4740
4888
|
}
|
|
4741
4889
|
const [cmd, ...cmdArgs] = options.command;
|
|
@@ -4746,15 +4894,20 @@ async function main() {
|
|
|
4746
4894
|
const host = options.localhost ? "127.0.0.1" : "0.0.0.0";
|
|
4747
4895
|
const ip = await resolveSessionIP(networkingMode, options.localhost);
|
|
4748
4896
|
const __dirname2 = dirname4(fileURLToPath4(import.meta.url));
|
|
4749
|
-
const webClientPath =
|
|
4897
|
+
const webClientPath = join7(__dirname2, "web-client");
|
|
4750
4898
|
const ptyManager = new PtyManager(cmd, cmdArgs);
|
|
4899
|
+
const sessionId = `${cmd}-${Date.now().toString(36)}`;
|
|
4900
|
+
const sessionLogger = new SessionLogger(sessionId);
|
|
4751
4901
|
const server = createSyncServer({
|
|
4752
4902
|
ptyManager,
|
|
4753
4903
|
token,
|
|
4754
4904
|
webClientPath,
|
|
4755
4905
|
host,
|
|
4756
4906
|
port,
|
|
4757
|
-
|
|
4907
|
+
resizePolicy: "last-writer-wins",
|
|
4908
|
+
scrollbackBufferSize: config.scrollbackBufferSize,
|
|
4909
|
+
clientBufferLimit: config.clientBufferLimit,
|
|
4910
|
+
logger: sessionLogger
|
|
4758
4911
|
});
|
|
4759
4912
|
let registeredSession = null;
|
|
4760
4913
|
let heartbeatInterval = null;
|
|
@@ -4805,6 +4958,11 @@ async function main() {
|
|
|
4805
4958
|
process.stdin.resume();
|
|
4806
4959
|
process.stdin.setEncoding("utf-8");
|
|
4807
4960
|
process.stdin.on("data", (data) => {
|
|
4961
|
+
if (process.stdout.columns && process.stdout.rows) {
|
|
4962
|
+
if (process.stdout.columns !== ptyManager.cols || process.stdout.rows !== ptyManager.rows) {
|
|
4963
|
+
server.resizeFromLocal(process.stdout.columns, process.stdout.rows);
|
|
4964
|
+
}
|
|
4965
|
+
}
|
|
4808
4966
|
ptyManager.write(data);
|
|
4809
4967
|
});
|
|
4810
4968
|
ptyManager.onData((data) => {
|
|
@@ -4812,8 +4970,7 @@ async function main() {
|
|
|
4812
4970
|
});
|
|
4813
4971
|
function handleResize() {
|
|
4814
4972
|
if (process.stdout.columns && process.stdout.rows) {
|
|
4815
|
-
|
|
4816
|
-
server.broadcastResize(process.stdout.columns, process.stdout.rows);
|
|
4973
|
+
server.resizeFromLocal(process.stdout.columns, process.stdout.rows);
|
|
4817
4974
|
}
|
|
4818
4975
|
}
|
|
4819
4976
|
process.stdout.on("resize", handleResize);
|
|
@@ -4835,6 +4992,7 @@ async function main() {
|
|
|
4835
4992
|
sleepGuard?.kill();
|
|
4836
4993
|
server.close();
|
|
4837
4994
|
ptyManager.kill();
|
|
4995
|
+
await sessionLogger.close();
|
|
4838
4996
|
}
|
|
4839
4997
|
ptyManager.onExit(async (exitCode) => {
|
|
4840
4998
|
console.log(`
|