fied 0.2.1 → 0.2.3

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.
Files changed (2) hide show
  1. package/dist/bin.js +89 -24
  2. 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,7 @@ 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);
217
234
  const onUrl = (url) => {
218
235
  if (options.background) {
219
236
  addSession({
@@ -224,6 +241,7 @@ async function share(options) {
224
241
  startedAt: (/* @__PURE__ */ new Date()).toISOString()
225
242
  });
226
243
  }
244
+ return options.onShareUrl?.(url);
227
245
  };
228
246
  await bridge.connect(onUrl);
229
247
  let closed = false;
@@ -290,12 +308,13 @@ async function createSession(relayHttpBase) {
290
308
  }
291
309
  var WS_CONNECT_TIMEOUT_MS = 1e4;
292
310
  var RelayBridge = class {
293
- constructor(relayTarget, key, keyFragment, pty, silent = false) {
311
+ constructor(relayTarget, key, keyFragment, pty, silent = false, sessionId) {
294
312
  this.relayTarget = relayTarget;
295
313
  this.key = key;
296
314
  this.keyFragment = keyFragment;
297
315
  this.pty = pty;
298
316
  this.silent = silent;
317
+ this.sessionId = sessionId ?? null;
299
318
  this.pty.onData((data) => {
300
319
  if (this.ws?.readyState === WebSocket.OPEN) {
301
320
  this.sendEncrypted(MSG_TERMINAL_OUTPUT, this.encoder.encode(data));
@@ -327,7 +346,6 @@ var RelayBridge = class {
327
346
  }
328
347
  const shareUrl = new URL(`s/${this.sessionId}`, this.relayTarget.httpBase);
329
348
  const url = `${shareUrl.toString()}#${this.keyFragment}`;
330
- this.onUrl?.(url);
331
349
  if (!this.silent) {
332
350
  console.log(` \x1B[1mShare this link:\x1B[0m`);
333
351
  console.log(` \x1B[4m\x1B[36m${url}\x1B[0m`);
@@ -336,6 +354,7 @@ var RelayBridge = class {
336
354
  console.log(" \x1B[2mPress Ctrl+C to stop sharing.\x1B[0m");
337
355
  console.log("");
338
356
  }
357
+ void this.onUrl?.(url);
339
358
  }
340
359
  const wsUrl = new URL(`api/sessions/${this.sessionId}/ws`, this.relayTarget.wsBase);
341
360
  wsUrl.searchParams.set("role", "host");
@@ -359,6 +378,8 @@ var RelayBridge = class {
359
378
  const text = this.decoder.decode(raw);
360
379
  if (text === "__fied_ping__") {
361
380
  ws.send("__fied_pong__");
381
+ } else if (text === VIEWER_JOINED) {
382
+ this.pty.write("\f");
362
383
  }
363
384
  return;
364
385
  }
@@ -543,6 +564,10 @@ if (args.includes("--__daemon")) {
543
564
  options.relay = args[++i];
544
565
  } else if (args[i] === "--allow-insecure-relay") {
545
566
  options.allowInsecureRelay = true;
567
+ } else if (args[i] === "--__session-id" && args[i + 1]) {
568
+ options.sessionId = args[++i];
569
+ } else if (args[i] === "--__key" && args[i + 1]) {
570
+ options.keyBase64Url = args[++i];
546
571
  }
547
572
  }
548
573
  share({ ...options, background: true }).catch(() => process.exit(1));
@@ -618,26 +643,66 @@ async function main() {
618
643
  session = session.split(" \u2014 ")[0].trim();
619
644
  }
620
645
  }
621
- const background = await confirm("Run in background?");
622
- if (background) {
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 });
646
+ if (!session) {
647
+ throw new Error("No tmux session selected");
640
648
  }
649
+ await share({
650
+ session,
651
+ relay,
652
+ allowInsecureRelay,
653
+ onShareUrl: async (url) => {
654
+ const background = await confirm("Run in background?");
655
+ if (!background) {
656
+ return;
657
+ }
658
+ const parsed = parseShareUrl(url);
659
+ const child = spawnBackground({
660
+ session,
661
+ relay,
662
+ allowInsecureRelay,
663
+ sessionId: parsed.sessionId,
664
+ keyBase64Url: parsed.keyBase64Url
665
+ });
666
+ console.error("");
667
+ console.error(` \x1B[1m\x1B[32mfied\x1B[0m \u2014 moved to background (PID ${child.pid})`);
668
+ console.error(` Session: ${session}`);
669
+ console.error(" Same share link stays active.");
670
+ console.error(" Run \x1B[1mnpx fied\x1B[0m again to manage.");
671
+ console.error("");
672
+ setTimeout(() => process.exit(0), 300);
673
+ }
674
+ });
675
+ }
676
+ function spawnBackground(options) {
677
+ const binPath = fileURLToPath(import.meta.url);
678
+ const childArgs = [
679
+ "--__daemon",
680
+ "--session",
681
+ options.session,
682
+ "--__session-id",
683
+ options.sessionId,
684
+ "--__key",
685
+ options.keyBase64Url
686
+ ];
687
+ if (options.relay) childArgs.push("--relay", options.relay);
688
+ if (options.allowInsecureRelay) childArgs.push("--allow-insecure-relay");
689
+ const child = spawnChild(process.execPath, [binPath, ...childArgs], {
690
+ detached: true,
691
+ stdio: "ignore"
692
+ });
693
+ child.unref();
694
+ return child;
695
+ }
696
+ function parseShareUrl(url) {
697
+ const parsed = new URL(url);
698
+ const match = parsed.pathname.match(/^\/s\/([A-Za-z0-9_-]{8,64})$/);
699
+ if (!match || !parsed.hash) {
700
+ throw new Error("Invalid share URL");
701
+ }
702
+ return {
703
+ sessionId: match[1],
704
+ keyBase64Url: parsed.hash.slice(1)
705
+ };
641
706
  }
642
707
  function timeSince(date) {
643
708
  const seconds = Math.floor((Date.now() - date.getTime()) / 1e3);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fied",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Share your tmux session in the browser with end-to-end encryption",
5
5
  "type": "module",
6
6
  "bin": {