fied 0.2.1 → 0.2.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/bin.js +105 -24
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -52,6 +52,22 @@ function toBase64Url(bytes) {
|
|
|
52
52
|
}
|
|
53
53
|
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
54
54
|
}
|
|
55
|
+
function fromBase64Url(str) {
|
|
56
|
+
let base64 = str.replace(/-/g, "+").replace(/_/g, "/");
|
|
57
|
+
while (base64.length % 4 !== 0) {
|
|
58
|
+
base64 += "=";
|
|
59
|
+
}
|
|
60
|
+
if (typeof atob === "function") {
|
|
61
|
+
const binary = atob(base64);
|
|
62
|
+
const bytes = new Uint8Array(binary.length);
|
|
63
|
+
for (let i = 0; i < binary.length; i++) {
|
|
64
|
+
bytes[i] = binary.charCodeAt(i);
|
|
65
|
+
}
|
|
66
|
+
return bytes;
|
|
67
|
+
} else {
|
|
68
|
+
return new Uint8Array(Buffer.from(base64, "base64"));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
55
71
|
|
|
56
72
|
// ../crypto/dist/protocol.js
|
|
57
73
|
var IV_LENGTH2 = 12;
|
|
@@ -166,6 +182,7 @@ var DEFAULT_RELAY = "https://fied.app";
|
|
|
166
182
|
var MSG_TERMINAL_OUTPUT = 1;
|
|
167
183
|
var MSG_TERMINAL_INPUT = 2;
|
|
168
184
|
var MSG_RESIZE = 3;
|
|
185
|
+
var VIEWER_JOINED = "__fied_viewer_joined__";
|
|
169
186
|
var RESIZE_MIN_COLS = 20;
|
|
170
187
|
var RESIZE_MAX_COLS = 1e3;
|
|
171
188
|
var RESIZE_MIN_ROWS = 5;
|
|
@@ -201,9 +218,9 @@ async function share(options) {
|
|
|
201
218
|
}
|
|
202
219
|
const cols = options.cols ?? process.stdout.columns ?? 80;
|
|
203
220
|
const rows = options.rows ?? process.stdout.rows ?? 24;
|
|
204
|
-
const rawKey = await generateKey();
|
|
221
|
+
const rawKey = options.keyBase64Url ? fromBase64Url(options.keyBase64Url) : await generateKey();
|
|
205
222
|
const cryptoKey = await importKey(rawKey);
|
|
206
|
-
const keyFragment = toBase64Url(rawKey);
|
|
223
|
+
const keyFragment = options.keyBase64Url ?? toBase64Url(rawKey);
|
|
207
224
|
const pty = attachSession(targetSession, cols, rows);
|
|
208
225
|
if (!options.background) {
|
|
209
226
|
console.log("");
|
|
@@ -213,7 +230,8 @@ async function share(options) {
|
|
|
213
230
|
console.log(` Size: ${cols}x${rows}`);
|
|
214
231
|
console.log("");
|
|
215
232
|
}
|
|
216
|
-
const bridge = new RelayBridge(relayTarget, cryptoKey, keyFragment, pty, options.background);
|
|
233
|
+
const bridge = new RelayBridge(relayTarget, cryptoKey, keyFragment, pty, options.background, options.sessionId);
|
|
234
|
+
let handoffRequested = false;
|
|
217
235
|
const onUrl = (url) => {
|
|
218
236
|
if (options.background) {
|
|
219
237
|
addSession({
|
|
@@ -224,8 +242,24 @@ async function share(options) {
|
|
|
224
242
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
225
243
|
});
|
|
226
244
|
}
|
|
245
|
+
if (options.onShareUrl) {
|
|
246
|
+
return Promise.resolve(options.onShareUrl(url)).then((action) => {
|
|
247
|
+
if (action === "handoff") {
|
|
248
|
+
handoffRequested = true;
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
return Promise.resolve();
|
|
227
253
|
};
|
|
228
254
|
await bridge.connect(onUrl);
|
|
255
|
+
if (handoffRequested) {
|
|
256
|
+
bridge.destroy();
|
|
257
|
+
try {
|
|
258
|
+
pty.kill();
|
|
259
|
+
} catch {
|
|
260
|
+
}
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
229
263
|
let closed = false;
|
|
230
264
|
const cleanup = () => {
|
|
231
265
|
if (closed) return;
|
|
@@ -290,12 +324,13 @@ async function createSession(relayHttpBase) {
|
|
|
290
324
|
}
|
|
291
325
|
var WS_CONNECT_TIMEOUT_MS = 1e4;
|
|
292
326
|
var RelayBridge = class {
|
|
293
|
-
constructor(relayTarget, key, keyFragment, pty, silent = false) {
|
|
327
|
+
constructor(relayTarget, key, keyFragment, pty, silent = false, sessionId) {
|
|
294
328
|
this.relayTarget = relayTarget;
|
|
295
329
|
this.key = key;
|
|
296
330
|
this.keyFragment = keyFragment;
|
|
297
331
|
this.pty = pty;
|
|
298
332
|
this.silent = silent;
|
|
333
|
+
this.sessionId = sessionId ?? null;
|
|
299
334
|
this.pty.onData((data) => {
|
|
300
335
|
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
301
336
|
this.sendEncrypted(MSG_TERMINAL_OUTPUT, this.encoder.encode(data));
|
|
@@ -327,7 +362,6 @@ var RelayBridge = class {
|
|
|
327
362
|
}
|
|
328
363
|
const shareUrl = new URL(`s/${this.sessionId}`, this.relayTarget.httpBase);
|
|
329
364
|
const url = `${shareUrl.toString()}#${this.keyFragment}`;
|
|
330
|
-
this.onUrl?.(url);
|
|
331
365
|
if (!this.silent) {
|
|
332
366
|
console.log(` \x1B[1mShare this link:\x1B[0m`);
|
|
333
367
|
console.log(` \x1B[4m\x1B[36m${url}\x1B[0m`);
|
|
@@ -336,6 +370,7 @@ var RelayBridge = class {
|
|
|
336
370
|
console.log(" \x1B[2mPress Ctrl+C to stop sharing.\x1B[0m");
|
|
337
371
|
console.log("");
|
|
338
372
|
}
|
|
373
|
+
await this.onUrl?.(url);
|
|
339
374
|
}
|
|
340
375
|
const wsUrl = new URL(`api/sessions/${this.sessionId}/ws`, this.relayTarget.wsBase);
|
|
341
376
|
wsUrl.searchParams.set("role", "host");
|
|
@@ -359,6 +394,8 @@ var RelayBridge = class {
|
|
|
359
394
|
const text = this.decoder.decode(raw);
|
|
360
395
|
if (text === "__fied_ping__") {
|
|
361
396
|
ws.send("__fied_pong__");
|
|
397
|
+
} else if (text === VIEWER_JOINED) {
|
|
398
|
+
this.pty.write("\f");
|
|
362
399
|
}
|
|
363
400
|
return;
|
|
364
401
|
}
|
|
@@ -543,6 +580,10 @@ if (args.includes("--__daemon")) {
|
|
|
543
580
|
options.relay = args[++i];
|
|
544
581
|
} else if (args[i] === "--allow-insecure-relay") {
|
|
545
582
|
options.allowInsecureRelay = true;
|
|
583
|
+
} else if (args[i] === "--__session-id" && args[i + 1]) {
|
|
584
|
+
options.sessionId = args[++i];
|
|
585
|
+
} else if (args[i] === "--__key" && args[i + 1]) {
|
|
586
|
+
options.keyBase64Url = args[++i];
|
|
546
587
|
}
|
|
547
588
|
}
|
|
548
589
|
share({ ...options, background: true }).catch(() => process.exit(1));
|
|
@@ -618,26 +659,66 @@ async function main() {
|
|
|
618
659
|
session = session.split(" \u2014 ")[0].trim();
|
|
619
660
|
}
|
|
620
661
|
}
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
const binPath = fileURLToPath(import.meta.url);
|
|
624
|
-
const childArgs = ["--__daemon", "--session", session];
|
|
625
|
-
if (relay) childArgs.push("--relay", relay);
|
|
626
|
-
if (allowInsecureRelay) childArgs.push("--allow-insecure-relay");
|
|
627
|
-
const child = spawnChild(process.execPath, [binPath, ...childArgs], {
|
|
628
|
-
detached: true,
|
|
629
|
-
stdio: "ignore"
|
|
630
|
-
});
|
|
631
|
-
child.unref();
|
|
632
|
-
console.error("");
|
|
633
|
-
console.error(` \x1B[1m\x1B[32mfied\x1B[0m \u2014 started in background (PID ${child.pid})`);
|
|
634
|
-
console.error(` Session: ${session}`);
|
|
635
|
-
console.error(" Run \x1B[1mnpx fied\x1B[0m again to manage.");
|
|
636
|
-
console.error("");
|
|
637
|
-
setTimeout(() => process.exit(0), 500);
|
|
638
|
-
} else {
|
|
639
|
-
await share({ session, relay, allowInsecureRelay });
|
|
662
|
+
if (!session) {
|
|
663
|
+
throw new Error("No tmux session selected");
|
|
640
664
|
}
|
|
665
|
+
await share({
|
|
666
|
+
session,
|
|
667
|
+
relay,
|
|
668
|
+
allowInsecureRelay,
|
|
669
|
+
onShareUrl: async (url) => {
|
|
670
|
+
const background = await confirm("Run in background?");
|
|
671
|
+
if (!background) {
|
|
672
|
+
return "continue";
|
|
673
|
+
}
|
|
674
|
+
const parsed = parseShareUrl(url);
|
|
675
|
+
const child = spawnBackground({
|
|
676
|
+
session,
|
|
677
|
+
relay,
|
|
678
|
+
allowInsecureRelay,
|
|
679
|
+
sessionId: parsed.sessionId,
|
|
680
|
+
keyBase64Url: parsed.keyBase64Url
|
|
681
|
+
});
|
|
682
|
+
console.error("");
|
|
683
|
+
console.error(` \x1B[1m\x1B[32mfied\x1B[0m \u2014 moved to background (PID ${child.pid})`);
|
|
684
|
+
console.error(` Session: ${session}`);
|
|
685
|
+
console.error(" Same share link stays active.");
|
|
686
|
+
console.error(" Run \x1B[1mnpx fied\x1B[0m again to manage.");
|
|
687
|
+
console.error("");
|
|
688
|
+
return "handoff";
|
|
689
|
+
}
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
function spawnBackground(options) {
|
|
693
|
+
const binPath = fileURLToPath(import.meta.url);
|
|
694
|
+
const childArgs = [
|
|
695
|
+
"--__daemon",
|
|
696
|
+
"--session",
|
|
697
|
+
options.session,
|
|
698
|
+
"--__session-id",
|
|
699
|
+
options.sessionId,
|
|
700
|
+
"--__key",
|
|
701
|
+
options.keyBase64Url
|
|
702
|
+
];
|
|
703
|
+
if (options.relay) childArgs.push("--relay", options.relay);
|
|
704
|
+
if (options.allowInsecureRelay) childArgs.push("--allow-insecure-relay");
|
|
705
|
+
const child = spawnChild(process.execPath, [binPath, ...childArgs], {
|
|
706
|
+
detached: true,
|
|
707
|
+
stdio: "ignore"
|
|
708
|
+
});
|
|
709
|
+
child.unref();
|
|
710
|
+
return child;
|
|
711
|
+
}
|
|
712
|
+
function parseShareUrl(url) {
|
|
713
|
+
const parsed = new URL(url);
|
|
714
|
+
const match = parsed.pathname.match(/^\/s\/([A-Za-z0-9_-]{8,64})$/);
|
|
715
|
+
if (!match || !parsed.hash) {
|
|
716
|
+
throw new Error("Invalid share URL");
|
|
717
|
+
}
|
|
718
|
+
return {
|
|
719
|
+
sessionId: match[1],
|
|
720
|
+
keyBase64Url: parsed.hash.slice(1)
|
|
721
|
+
};
|
|
641
722
|
}
|
|
642
723
|
function timeSince(date) {
|
|
643
724
|
const seconds = Math.floor((Date.now() - date.getTime()) / 1e3);
|