airloom 0.1.6 → 0.1.7
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/index.js +189 -14
- package/dist/viewer/assets/{browser-CHRQqYPW.js → browser-BRKgrEB1.js} +1 -1
- package/dist/viewer/assets/index-CCYyPnVc.js +52 -0
- package/dist/viewer/assets/index-D9J9LZLl.css +32 -0
- package/dist/viewer/assets/{index-m5PBOzJQ.js → index-DKFwSRim.js} +1 -1
- package/dist/viewer/index.html +14 -15
- package/package.json +1 -1
- package/dist/viewer/assets/index-DLLnAccl.js +0 -72
- package/dist/viewer/assets/index-mDTBX0lq.css +0 -1
package/dist/index.js
CHANGED
|
@@ -267,7 +267,7 @@ var Channel = class extends EventEmitter2 {
|
|
|
267
267
|
}
|
|
268
268
|
waitForReady(timeoutMs = 3e4) {
|
|
269
269
|
if (this._ready) return Promise.resolve();
|
|
270
|
-
return new Promise((
|
|
270
|
+
return new Promise((resolve4, reject) => {
|
|
271
271
|
const cleanup = () => {
|
|
272
272
|
clearTimeout(timer);
|
|
273
273
|
this.removeListener("ready", onReady);
|
|
@@ -276,7 +276,7 @@ var Channel = class extends EventEmitter2 {
|
|
|
276
276
|
};
|
|
277
277
|
const onReady = () => {
|
|
278
278
|
cleanup();
|
|
279
|
-
|
|
279
|
+
resolve4();
|
|
280
280
|
};
|
|
281
281
|
const onError = (err) => {
|
|
282
282
|
cleanup();
|
|
@@ -373,14 +373,14 @@ var WebSocketAdapter = class {
|
|
|
373
373
|
}
|
|
374
374
|
async doConnect() {
|
|
375
375
|
const ws = await this.createWebSocket(this.url);
|
|
376
|
-
return new Promise((
|
|
376
|
+
return new Promise((resolve4, reject) => {
|
|
377
377
|
ws.onopen = () => {
|
|
378
378
|
this._connected = true;
|
|
379
379
|
this.shouldReconnect = true;
|
|
380
380
|
this.reconnectAttempts = 0;
|
|
381
381
|
const msg = this.role === "host" ? { type: "create", sessionToken: this.sessionToken } : { type: "join", sessionToken: this.sessionToken };
|
|
382
382
|
ws.send(JSON.stringify(msg));
|
|
383
|
-
|
|
383
|
+
resolve4();
|
|
384
384
|
};
|
|
385
385
|
ws.onmessage = (event) => {
|
|
386
386
|
const raw = event.data;
|
|
@@ -514,8 +514,8 @@ var AblyAdapter = class {
|
|
|
514
514
|
clientOpts.token = this.opts.token;
|
|
515
515
|
}
|
|
516
516
|
this.ably = new Realtime(clientOpts);
|
|
517
|
-
await new Promise((
|
|
518
|
-
this.ably.connection.once("connected", () =>
|
|
517
|
+
await new Promise((resolve4, reject) => {
|
|
518
|
+
this.ably.connection.once("connected", () => resolve4());
|
|
519
519
|
this.ably.connection.once("failed", (stateChange) => {
|
|
520
520
|
reject(new Error(stateChange?.reason?.message ?? "Ably connection failed"));
|
|
521
521
|
});
|
|
@@ -602,8 +602,8 @@ var AblyAdapter = class {
|
|
|
602
602
|
import { sha256 as sha2562 } from "@noble/hashes/sha256";
|
|
603
603
|
import { networkInterfaces } from "os";
|
|
604
604
|
import { fileURLToPath } from "url";
|
|
605
|
-
import { dirname, resolve as
|
|
606
|
-
import { existsSync as
|
|
605
|
+
import { dirname, resolve as resolve3 } from "path";
|
|
606
|
+
import { existsSync as existsSync4 } from "fs";
|
|
607
607
|
import QRCode from "qrcode";
|
|
608
608
|
|
|
609
609
|
// src/server.ts
|
|
@@ -1148,11 +1148,11 @@ function createHostServer(opts) {
|
|
|
1148
1148
|
if (ws.readyState === WebSocket.OPEN) ws.send(msg);
|
|
1149
1149
|
}
|
|
1150
1150
|
}
|
|
1151
|
-
return new Promise((
|
|
1151
|
+
return new Promise((resolve4) => {
|
|
1152
1152
|
server.listen(opts.port, "0.0.0.0", () => {
|
|
1153
1153
|
const addr = server.address();
|
|
1154
1154
|
const actualPort = typeof addr === "object" && addr ? addr.port : opts.port;
|
|
1155
|
-
|
|
1155
|
+
resolve4({ server, broadcast, port: actualPort });
|
|
1156
1156
|
});
|
|
1157
1157
|
server.on("error", (err) => {
|
|
1158
1158
|
console.error(`[host] Server error: ${err.message}`);
|
|
@@ -1359,6 +1359,174 @@ function addMsg(role,content){const el=document.createElement('div');el.classNam
|
|
|
1359
1359
|
</body>
|
|
1360
1360
|
</html>`;
|
|
1361
1361
|
|
|
1362
|
+
// src/terminal.ts
|
|
1363
|
+
import { basename, delimiter as delimiter2, isAbsolute as isAbsolute2, join as join3, resolve as resolve2 } from "path";
|
|
1364
|
+
import { existsSync as existsSync3 } from "fs";
|
|
1365
|
+
import { spawn as spawn2 } from "node-pty";
|
|
1366
|
+
function resolveExecutable2(command, envPath = process.env.PATH ?? "") {
|
|
1367
|
+
if (!command) return null;
|
|
1368
|
+
if (isAbsolute2(command) && existsSync3(command)) return command;
|
|
1369
|
+
if (command.includes("/")) {
|
|
1370
|
+
const candidate = resolve2(process.cwd(), command);
|
|
1371
|
+
return existsSync3(candidate) ? candidate : null;
|
|
1372
|
+
}
|
|
1373
|
+
for (const dir of envPath.split(delimiter2)) {
|
|
1374
|
+
if (!dir) continue;
|
|
1375
|
+
const candidate = join3(dir.replace(/^~(?=$|\/)/, process.env.HOME ?? "~"), command);
|
|
1376
|
+
if (existsSync3(candidate)) return candidate;
|
|
1377
|
+
}
|
|
1378
|
+
return null;
|
|
1379
|
+
}
|
|
1380
|
+
function parseCommand(command) {
|
|
1381
|
+
const parts = command.match(/(?:[^\s"]+|"[^"]*")+/g) ?? [command];
|
|
1382
|
+
return {
|
|
1383
|
+
file: parts[0],
|
|
1384
|
+
args: parts.slice(1).map((part) => part.replace(/^"|"$/g, ""))
|
|
1385
|
+
};
|
|
1386
|
+
}
|
|
1387
|
+
function getDefaultTerminalCommand() {
|
|
1388
|
+
const configured = process.env.AIRLOOM_TERMINAL_COMMAND?.trim();
|
|
1389
|
+
if (configured) return parseCommand(configured);
|
|
1390
|
+
if (process.platform === "win32") {
|
|
1391
|
+
const file = process.env.COMSPEC || "powershell.exe";
|
|
1392
|
+
return { file, args: [] };
|
|
1393
|
+
}
|
|
1394
|
+
const shell = process.env.SHELL || "/bin/bash";
|
|
1395
|
+
const name = basename(shell);
|
|
1396
|
+
if (name === "bash" || name === "zsh" || name === "sh") return { file: shell, args: ["-il"] };
|
|
1397
|
+
return { file: shell, args: ["-i"] };
|
|
1398
|
+
}
|
|
1399
|
+
var AdaptiveOutputBatcher = class {
|
|
1400
|
+
constructor(onFlush, fastInterval = 16, slowInterval = 80, interactiveWindow = 250, maxBytes = 4096) {
|
|
1401
|
+
this.onFlush = onFlush;
|
|
1402
|
+
this.fastInterval = fastInterval;
|
|
1403
|
+
this.slowInterval = slowInterval;
|
|
1404
|
+
this.interactiveWindow = interactiveWindow;
|
|
1405
|
+
this.maxBytes = maxBytes;
|
|
1406
|
+
}
|
|
1407
|
+
buffer = "";
|
|
1408
|
+
timer = null;
|
|
1409
|
+
lastInputAt = 0;
|
|
1410
|
+
noteInput() {
|
|
1411
|
+
this.lastInputAt = Date.now();
|
|
1412
|
+
if (this.timer) {
|
|
1413
|
+
clearTimeout(this.timer);
|
|
1414
|
+
this.timer = null;
|
|
1415
|
+
}
|
|
1416
|
+
if (this.buffer) this.schedule();
|
|
1417
|
+
}
|
|
1418
|
+
write(data) {
|
|
1419
|
+
this.buffer += data;
|
|
1420
|
+
if (this.buffer.length >= this.maxBytes) {
|
|
1421
|
+
this.flush();
|
|
1422
|
+
return;
|
|
1423
|
+
}
|
|
1424
|
+
this.schedule();
|
|
1425
|
+
}
|
|
1426
|
+
flush() {
|
|
1427
|
+
if (this.timer) {
|
|
1428
|
+
clearTimeout(this.timer);
|
|
1429
|
+
this.timer = null;
|
|
1430
|
+
}
|
|
1431
|
+
if (!this.buffer) return;
|
|
1432
|
+
const data = this.buffer;
|
|
1433
|
+
this.buffer = "";
|
|
1434
|
+
this.onFlush(data);
|
|
1435
|
+
}
|
|
1436
|
+
destroy() {
|
|
1437
|
+
this.flush();
|
|
1438
|
+
}
|
|
1439
|
+
schedule() {
|
|
1440
|
+
if (this.timer) return;
|
|
1441
|
+
const recentInput = Date.now() - this.lastInputAt <= this.interactiveWindow;
|
|
1442
|
+
this.timer = setTimeout(() => this.flush(), recentInput ? this.fastInterval : this.slowInterval);
|
|
1443
|
+
}
|
|
1444
|
+
};
|
|
1445
|
+
var TerminalSession = class {
|
|
1446
|
+
constructor(channel) {
|
|
1447
|
+
this.channel = channel;
|
|
1448
|
+
}
|
|
1449
|
+
pty = null;
|
|
1450
|
+
stream = null;
|
|
1451
|
+
batcher = null;
|
|
1452
|
+
cols = 120;
|
|
1453
|
+
rows = 36;
|
|
1454
|
+
handleMessage(message) {
|
|
1455
|
+
switch (message.type) {
|
|
1456
|
+
case "terminal_open":
|
|
1457
|
+
this.open(message);
|
|
1458
|
+
break;
|
|
1459
|
+
case "terminal_input":
|
|
1460
|
+
this.writeInput(message);
|
|
1461
|
+
break;
|
|
1462
|
+
case "terminal_resize":
|
|
1463
|
+
this.resize(message);
|
|
1464
|
+
break;
|
|
1465
|
+
case "terminal_close":
|
|
1466
|
+
this.close();
|
|
1467
|
+
break;
|
|
1468
|
+
case "terminal_exit":
|
|
1469
|
+
break;
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
close() {
|
|
1473
|
+
this.batcher?.destroy();
|
|
1474
|
+
this.batcher = null;
|
|
1475
|
+
if (this.stream && !this.stream.ended) this.stream.end();
|
|
1476
|
+
this.stream = null;
|
|
1477
|
+
try {
|
|
1478
|
+
this.pty?.kill();
|
|
1479
|
+
} catch {
|
|
1480
|
+
}
|
|
1481
|
+
this.pty = null;
|
|
1482
|
+
}
|
|
1483
|
+
open(message) {
|
|
1484
|
+
this.cols = Math.max(20, Math.floor(message.cols || this.cols));
|
|
1485
|
+
this.rows = Math.max(5, Math.floor(message.rows || this.rows));
|
|
1486
|
+
if (this.pty) {
|
|
1487
|
+
this.pty.resize(this.cols, this.rows);
|
|
1488
|
+
return;
|
|
1489
|
+
}
|
|
1490
|
+
const command = getDefaultTerminalCommand();
|
|
1491
|
+
const file = resolveExecutable2(command.file) ?? command.file;
|
|
1492
|
+
const meta = { kind: "terminal", cols: this.cols, rows: this.rows };
|
|
1493
|
+
this.stream = this.channel.createStream(meta);
|
|
1494
|
+
this.batcher = new AdaptiveOutputBatcher((data) => this.stream?.write(data));
|
|
1495
|
+
const env = { ...process.env, TERM: "xterm-256color" };
|
|
1496
|
+
this.pty = spawn2(file, command.args, {
|
|
1497
|
+
name: "xterm-256color",
|
|
1498
|
+
cols: this.cols,
|
|
1499
|
+
rows: this.rows,
|
|
1500
|
+
cwd: process.cwd(),
|
|
1501
|
+
env
|
|
1502
|
+
});
|
|
1503
|
+
this.pty.onData((data) => this.batcher?.write(data));
|
|
1504
|
+
this.pty.onExit(({ exitCode, signal }) => {
|
|
1505
|
+
this.batcher?.flush();
|
|
1506
|
+
this.stream?.end();
|
|
1507
|
+
this.channel.send({ type: "terminal_exit", exitCode, signal });
|
|
1508
|
+
this.stream = null;
|
|
1509
|
+
this.batcher = null;
|
|
1510
|
+
this.pty = null;
|
|
1511
|
+
});
|
|
1512
|
+
}
|
|
1513
|
+
writeInput(message) {
|
|
1514
|
+
if (!this.pty) return;
|
|
1515
|
+
this.batcher?.noteInput();
|
|
1516
|
+
this.pty.write(message.data);
|
|
1517
|
+
}
|
|
1518
|
+
resize(message) {
|
|
1519
|
+
this.cols = Math.max(20, Math.floor(message.cols || this.cols));
|
|
1520
|
+
this.rows = Math.max(5, Math.floor(message.rows || this.rows));
|
|
1521
|
+
this.pty?.resize(this.cols, this.rows);
|
|
1522
|
+
}
|
|
1523
|
+
};
|
|
1524
|
+
function isTerminalMessage(data) {
|
|
1525
|
+
if (!data || typeof data !== "object" || !("type" in data)) return false;
|
|
1526
|
+
const type = data.type;
|
|
1527
|
+
return type === "terminal_open" || type === "terminal_input" || type === "terminal_resize" || type === "terminal_close" || type === "terminal_exit";
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1362
1530
|
// src/index.ts
|
|
1363
1531
|
function parseArgs(argv) {
|
|
1364
1532
|
const args = {};
|
|
@@ -1437,10 +1605,10 @@ function getLanIP() {
|
|
|
1437
1605
|
return void 0;
|
|
1438
1606
|
}
|
|
1439
1607
|
function resolveViewerDir() {
|
|
1440
|
-
const prod =
|
|
1441
|
-
if (
|
|
1442
|
-
const dev =
|
|
1443
|
-
if (
|
|
1608
|
+
const prod = resolve3(__dirname, "viewer");
|
|
1609
|
+
if (existsSync4(prod)) return prod;
|
|
1610
|
+
const dev = resolve3(__dirname, "../../viewer/dist");
|
|
1611
|
+
if (existsSync4(dev)) return dev;
|
|
1444
1612
|
return void 0;
|
|
1445
1613
|
}
|
|
1446
1614
|
async function main() {
|
|
@@ -1601,6 +1769,7 @@ async function main() {
|
|
|
1601
1769
|
exec(`${cmd} ${localUrl}`);
|
|
1602
1770
|
}).catch(() => {
|
|
1603
1771
|
});
|
|
1772
|
+
const terminal = new TerminalSession(channel);
|
|
1604
1773
|
channel.on("ready", () => {
|
|
1605
1774
|
console.log("[host] Phone connected! Channel ready.");
|
|
1606
1775
|
state.connected = true;
|
|
@@ -1609,9 +1778,14 @@ async function main() {
|
|
|
1609
1778
|
channel.on("peer_left", () => {
|
|
1610
1779
|
console.log("[host] Phone disconnected.");
|
|
1611
1780
|
state.connected = false;
|
|
1781
|
+
terminal.close();
|
|
1612
1782
|
broadcast({ type: "peer_disconnected" });
|
|
1613
1783
|
});
|
|
1614
1784
|
channel.on("message", (data) => {
|
|
1785
|
+
if (isTerminalMessage(data)) {
|
|
1786
|
+
terminal.handleMessage(data);
|
|
1787
|
+
return;
|
|
1788
|
+
}
|
|
1615
1789
|
if (typeof data === "object" && data !== null && "type" in data && "content" in data) {
|
|
1616
1790
|
const msg = data;
|
|
1617
1791
|
if (msg.type === "chat" && typeof msg.content === "string") {
|
|
@@ -1630,6 +1804,7 @@ async function main() {
|
|
|
1630
1804
|
if (shuttingDown) return;
|
|
1631
1805
|
shuttingDown = true;
|
|
1632
1806
|
console.log("\n[host] Shutting down...");
|
|
1807
|
+
terminal.close();
|
|
1633
1808
|
state.adapter?.destroy?.();
|
|
1634
1809
|
try {
|
|
1635
1810
|
channel.close();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{g as c}from"./index-
|
|
1
|
+
import{g as c}from"./index-CCYyPnVc.js";function f(t,i){for(var o=0;o<i.length;o++){const e=i[o];if(typeof e!="string"&&!Array.isArray(e)){for(const r in e)if(r!=="default"&&!(r in t)){const s=Object.getOwnPropertyDescriptor(e,r);s&&Object.defineProperty(t,r,s.get?s:{enumerable:!0,get:()=>e[r]})}}}return Object.freeze(Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}))}var n,a;function b(){return a||(a=1,n=function(){throw new Error("ws does not work in the browser. Browser clients must use the native WebSocket object")}),n}var u=b();const w=c(u),p=f({__proto__:null,default:w},[u]);export{p as b};
|