tabby-rdp 0.4.1 → 0.5.0

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/desktops.js CHANGED
@@ -3,9 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DIRECT_KEY = exports.OWN_DESKTOP = void 0;
4
4
  exports.viaMatches = viaMatches;
5
5
  exports.specOf = specOf;
6
+ exports.hyperVSpec = hyperVSpec;
6
7
  exports.desktopsFor = desktopsFor;
7
8
  exports.desktopIdOf = desktopIdOf;
8
9
  exports.sessionKey = sessionKey;
10
+ const hyperv_1 = require("./hyperv");
9
11
  const wake_1 = require("./wake");
10
12
  exports.OWN_DESKTOP = 'own';
11
13
  /** Whether a `via` value names this SSH target: its alias (label), hostname, user@hostname, or full key. */
@@ -18,11 +20,20 @@ function viaMatches(via, target) {
18
20
  }
19
21
  /** The desktop an entry describes (a `remoteDesktop.desktops` entry, or an RDP profile's options), or null if unusable. */
20
22
  function specOf(extra) {
23
+ const hyperv = (0, hyperv_1.vmId)(extra?.hyperv);
24
+ if (hyperv) {
25
+ return hyperVSpec(hyperv, extra.name ? String(extra.name) : hyperv, {
26
+ username: extra.username ? String(extra.username) : undefined,
27
+ domain: extra.domain ? String(extra.domain) : undefined,
28
+ account: extra.account ? String(extra.account) : undefined,
29
+ });
30
+ }
21
31
  const port = Number(extra?.port || 3389);
22
32
  if (!Number.isInteger(port) || port <= 0 || port > 65535) {
23
33
  return null;
24
34
  }
25
35
  const host = String(extra.host || '127.0.0.1');
36
+ const gateway = typeof extra.gateway === 'string' ? extra.gateway.trim() : '';
26
37
  return {
27
38
  id: `${host}:${port}`,
28
39
  name: extra.name ? String(extra.name) : `${host}:${port}`,
@@ -33,8 +44,13 @@ function specOf(extra) {
33
44
  domain: extra.domain ? String(extra.domain) : undefined,
34
45
  account: extra.account ? String(extra.account) : undefined,
35
46
  wake: (0, wake_1.parseWake)(extra.wake),
47
+ ...gateway ? { gateway, gatewayAccount: extra.gatewayAccount ? String(extra.gatewayAccount) : undefined } : {},
36
48
  };
37
49
  }
50
+ /** A Hyper-V VM's console as a desktop of its host: the host's VMConnect port, the VM's id, started when it's off. */
51
+ function hyperVSpec(id, name, more = {}) {
52
+ return { id: `hyperv:${id}`, name, kind: 'windows', host: '127.0.0.1', port: hyperv_1.HYPERV_PORT, hyperv: id, wake: { hyperv: id }, ...more };
53
+ }
38
54
  /**
39
55
  * The SSH host's own desktop, then the configured desktops behind it, then `more` (RDP profiles going through this
40
56
  * host). `found`: what the setup found there the last time (none yet: GNOME, as the setup will try first). A host
@@ -73,7 +89,7 @@ function desktopsFor(target, extras, found, more = []) {
73
89
  exports.DIRECT_KEY = 'rdp';
74
90
  /** The id of a configured desktop (`host:port`), as its DesktopSpec and session keys have it. */
75
91
  function desktopIdOf(extra) {
76
- return `${extra.host || '127.0.0.1'}:${extra.port || 3389}`;
92
+ return (0, hyperv_1.vmId)(extra.hyperv) ? `hyperv:${(0, hyperv_1.vmId)(extra.hyperv)}` : `${extra.host || '127.0.0.1'}:${extra.port || 3389}`;
77
93
  }
78
94
  /**
79
95
  * One desktop per key: the remote account for its own desktop, plus the RDP server for one behind it (`rdp#<address>`
@@ -1 +1 @@
1
- {"version":3,"file":"desktops.js","sourceRoot":"","sources":["../src/desktops.ts"],"names":[],"mappings":";;;AAuEA,gCAMC;AAGD,wBAiBC;AASD,kCA0BC;AAMD,kCAEC;AAMD,gCAEC;AAnJD,iCAA4C;AAmE/B,QAAA,WAAW,GAAG,KAAK,CAAA;AAEhC,4GAA4G;AAC5G,SAAgB,UAAU,CAAE,GAAW,EAAE,MAAoB;IACzD,MAAM,CAAC,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;IAClC,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,WAAW,EAAE,CAAA,CAAE,qBAAqB;IAC3D,MAAM,QAAQ,GAAG,GAAG,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAA;IACzC,MAAM,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAA;IAChD,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAA;AACnF,CAAC;AAED,2HAA2H;AAC3H,SAAgB,MAAM,CAAE,KAAyB;IAC7C,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,EAAE,IAAI,IAAI,IAAI,CAAC,CAAA;IACxC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,IAAI,GAAG,KAAK,EAAE,CAAC;QACvD,OAAO,IAAI,CAAA;IACf,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,IAAI,WAAW,CAAC,CAAA;IAC9C,OAAO;QACH,EAAE,EAAE,GAAG,IAAI,IAAI,IAAI,EAAE;QACrB,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,IAAI,EAAE;QACzD,IAAI,EAAE,KAAK,CAAC,IAAI,KAAK,OAAO,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;QAC9E,IAAI;QACJ,IAAI;QACJ,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS;QAC7D,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS;QACvD,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS;QAC1D,IAAI,EAAE,IAAA,gBAAS,EAAC,KAAK,CAAC,IAAI,CAAC;KAC9B,CAAA;AACL,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,WAAW,CAAE,MAAoB,EAAE,MAAwC,EAAE,KAAuB,EAAE,OAA6B,EAAE;IACjJ,MAAM,GAAG,GAAgB,KAAK,EAAE,IAAI,KAAK,MAAM;QAC3C,CAAC,CAAC,EAAE,EAAE,EAAE,mBAAW,EAAE,IAAI,EAAE,GAAG,MAAM,CAAC,KAAK,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,KAAK,CAAC,QAAQ,IAAI,IAAI,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE;QAC/I,CAAC,CAAC,KAAK,EAAE,IAAI,KAAK,SAAS;YACvB,CAAC,CAAC,EAAE,EAAE,EAAE,mBAAW,EAAE,IAAI,EAAE,GAAG,MAAM,CAAC,KAAK,UAAU,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,IAAI,SAAS,EAAE;YAChK,CAAC,CAAC,EAAE,EAAE,EAAE,mBAAW,EAAE,IAAI,EAAE,GAAG,MAAM,CAAC,KAAK,UAAU,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,EAAE,CAAA;IACzG,MAAM,KAAK,GAAkB,CAAC,GAAG,CAAC,CAAA;IAClC,MAAM,MAAM,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,IAAI,UAAU,CAAC,KAAK,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAA;IACjH,KAAK,MAAM,KAAK,IAAI,CAAC,GAAG,MAAM,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC;QACvC,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAA;QAC1B,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;YAC7C,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACpB,CAAC;IACL,CAAC;IACD,uEAAuE;IACvE,IAAI,KAAK,EAAE,IAAI,KAAK,OAAO,IAAI,KAAK,CAAC,QAAQ,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,aAAa,KAAK,CAAC,QAAQ,EAAE,CAAC,EAAE,CAAC;QACxG,KAAK,CAAC,IAAI,CAAC;YACP,EAAE,EAAE,aAAa,KAAK,CAAC,QAAQ,EAAE;YACjC,IAAI,EAAE,GAAG,MAAM,CAAC,KAAK,iBAAiB;YACtC,IAAI,EAAE,MAAM;YACZ,IAAI,EAAE,WAAW;YACjB,IAAI,EAAE,KAAK,CAAC,QAAQ;YACpB,QAAQ,EAAE,KAAK,CAAC,QAAQ;SAC3B,CAAC,CAAA;IACN,CAAC;IACD,OAAO,KAAK,CAAA;AAChB,CAAC;AAED,0GAA0G;AAC7F,QAAA,UAAU,GAAG,KAAK,CAAA;AAE/B,iGAAiG;AACjG,SAAgB,WAAW,CAAE,KAAyB;IAClD,OAAO,GAAG,KAAK,CAAC,IAAI,IAAI,WAAW,IAAI,KAAK,CAAC,IAAI,IAAI,IAAI,EAAE,CAAA;AAC/D,CAAC;AAED;;;GAGG;AACH,SAAgB,UAAU,CAAE,MAAoB,EAAE,IAAiB;IAC/D,OAAO,IAAI,CAAC,EAAE,KAAK,mBAAW,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC,EAAE,EAAE,CAAA;AAC5E,CAAC"}
1
+ {"version":3,"file":"desktops.js","sourceRoot":"","sources":["../src/desktops.ts"],"names":[],"mappings":";;;AA+FA,gCAMC;AAGD,wBA2BC;AAGD,gCAEC;AASD,kCA0BC;AAMD,kCAEC;AAMD,gCAEC;AA3LD,qCAA4C;AAE5C,iCAA4C;AA0F/B,QAAA,WAAW,GAAG,KAAK,CAAA;AAEhC,4GAA4G;AAC5G,SAAgB,UAAU,CAAE,GAAW,EAAE,MAAoB;IACzD,MAAM,CAAC,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;IAClC,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,WAAW,EAAE,CAAA,CAAE,qBAAqB;IAC3D,MAAM,QAAQ,GAAG,GAAG,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAA;IACzC,MAAM,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAA;IAChD,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAA;AACnF,CAAC;AAED,2HAA2H;AAC3H,SAAgB,MAAM,CAAE,KAAyB;IAC7C,MAAM,MAAM,GAAG,IAAA,aAAI,EAAC,KAAK,EAAE,MAAM,CAAC,CAAA;IAClC,IAAI,MAAM,EAAE,CAAC;QACT,OAAO,UAAU,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE;YAChE,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS;YAC7D,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS;YACvD,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS;SAC7D,CAAC,CAAA;IACN,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,EAAE,IAAI,IAAI,IAAI,CAAC,CAAA;IACxC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,IAAI,GAAG,KAAK,EAAE,CAAC;QACvD,OAAO,IAAI,CAAA;IACf,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,IAAI,WAAW,CAAC,CAAA;IAC9C,MAAM,OAAO,GAAG,OAAO,KAAK,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;IAC7E,OAAO;QACH,EAAE,EAAE,GAAG,IAAI,IAAI,IAAI,EAAE;QACrB,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,IAAI,EAAE;QACzD,IAAI,EAAE,KAAK,CAAC,IAAI,KAAK,OAAO,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;QAC9E,IAAI;QACJ,IAAI;QACJ,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS;QAC7D,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS;QACvD,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS;QAC1D,IAAI,EAAE,IAAA,gBAAS,EAAC,KAAK,CAAC,IAAI,CAAC;QAC3B,GAAG,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,cAAc,EAAE,KAAK,CAAC,cAAc,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE;KACjH,CAAA;AACL,CAAC;AAED,sHAAsH;AACtH,SAAgB,UAAU,CAAE,EAAU,EAAE,IAAY,EAAE,OAA6B,EAAE;IACjF,OAAO,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,oBAAW,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,GAAG,IAAI,EAAE,CAAA;AACzI,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,WAAW,CAAE,MAAoB,EAAE,MAAwC,EAAE,KAAuB,EAAE,OAA6B,EAAE;IACjJ,MAAM,GAAG,GAAgB,KAAK,EAAE,IAAI,KAAK,MAAM;QAC3C,CAAC,CAAC,EAAE,EAAE,EAAE,mBAAW,EAAE,IAAI,EAAE,GAAG,MAAM,CAAC,KAAK,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,KAAK,CAAC,QAAQ,IAAI,IAAI,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE;QAC/I,CAAC,CAAC,KAAK,EAAE,IAAI,KAAK,SAAS;YACvB,CAAC,CAAC,EAAE,EAAE,EAAE,mBAAW,EAAE,IAAI,EAAE,GAAG,MAAM,CAAC,KAAK,UAAU,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,IAAI,SAAS,EAAE;YAChK,CAAC,CAAC,EAAE,EAAE,EAAE,mBAAW,EAAE,IAAI,EAAE,GAAG,MAAM,CAAC,KAAK,UAAU,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,EAAE,CAAA;IACzG,MAAM,KAAK,GAAkB,CAAC,GAAG,CAAC,CAAA;IAClC,MAAM,MAAM,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,IAAI,UAAU,CAAC,KAAK,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAA;IACjH,KAAK,MAAM,KAAK,IAAI,CAAC,GAAG,MAAM,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC;QACvC,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAA;QAC1B,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;YAC7C,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACpB,CAAC;IACL,CAAC;IACD,uEAAuE;IACvE,IAAI,KAAK,EAAE,IAAI,KAAK,OAAO,IAAI,KAAK,CAAC,QAAQ,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,aAAa,KAAK,CAAC,QAAQ,EAAE,CAAC,EAAE,CAAC;QACxG,KAAK,CAAC,IAAI,CAAC;YACP,EAAE,EAAE,aAAa,KAAK,CAAC,QAAQ,EAAE;YACjC,IAAI,EAAE,GAAG,MAAM,CAAC,KAAK,iBAAiB;YACtC,IAAI,EAAE,MAAM;YACZ,IAAI,EAAE,WAAW;YACjB,IAAI,EAAE,KAAK,CAAC,QAAQ;YACpB,QAAQ,EAAE,KAAK,CAAC,QAAQ;SAC3B,CAAC,CAAA;IACN,CAAC;IACD,OAAO,KAAK,CAAA;AAChB,CAAC;AAED,0GAA0G;AAC7F,QAAA,UAAU,GAAG,KAAK,CAAA;AAE/B,iGAAiG;AACjG,SAAgB,WAAW,CAAE,KAAyB;IAClD,OAAO,IAAA,aAAI,EAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,UAAU,IAAA,aAAI,EAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,IAAI,IAAI,WAAW,IAAI,KAAK,CAAC,IAAI,IAAI,IAAI,EAAE,CAAA;AACrH,CAAC;AAED;;;GAGG;AACH,SAAgB,UAAU,CAAE,MAAoB,EAAE,IAAiB;IAC/D,OAAO,IAAI,CAAC,EAAE,KAAK,mBAAW,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC,EAAE,EAAE,CAAA;AAC5E,CAAC"}
package/dist/drives.js ADDED
@@ -0,0 +1,284 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SharedDrives = void 0;
4
+ exports.sharedFolders = sharedFolders;
5
+ exports.driveName = driveName;
6
+ const fs = require("fs");
7
+ const path = require("path");
8
+ const fsp = fs.promises;
9
+ /** The folders configured, tidied: strings and booleans where expected, no duplicate paths, each with a name. */
10
+ function sharedFolders(store) {
11
+ const list = Array.isArray(store?.sharedFolders) ? store.sharedFolders : [];
12
+ const folders = [];
13
+ for (const entry of list) {
14
+ const folder = typeof entry?.path === 'string' ? entry.path.trim() : '';
15
+ if (!folder || folders.some(f => f.path === folder)) {
16
+ continue;
17
+ }
18
+ folders.push({
19
+ path: folder,
20
+ name: driveName(typeof entry.name === 'string' && entry.name.trim() ? entry.name : path.basename(folder) || folder, folders),
21
+ readOnly: entry.readOnly === true,
22
+ });
23
+ }
24
+ return folders;
25
+ }
26
+ /**
27
+ * A drive name the remote can show: no characters a Windows file name can't have, not empty, and not another
28
+ * shared folder's name (a number is added).
29
+ */
30
+ function driveName(wanted, others) {
31
+ const base = wanted.replace(/[\\/:*?"<>|\u0000-\u001f]/g, ' ').replace(/\s+/g, ' ').trim().slice(0, 64) || 'Folder';
32
+ let name = base;
33
+ for (let n = 2; others.some(o => o.name.toLowerCase() === name.toLowerCase()); n++) {
34
+ name = `${base} ${n}`;
35
+ }
36
+ return name;
37
+ }
38
+ /** An error with a Node-style `code`, which IronRDP maps to the matching NT status for the server. */
39
+ function fail(code, message) {
40
+ const error = new Error(message);
41
+ error.code = code;
42
+ throw error;
43
+ }
44
+ /** One read's worth: the server asks for 64 KiB or so; the proxy takes messages up to 32 MiB. */
45
+ const READ_LIMIT = 4 * 1024 * 1024;
46
+ /**
47
+ * The shared folders as the remote desktop's file system (MS-RDPEFS drive redirection), on Tabby's Node side.
48
+ * IronRDP asks one thing at a time and waits for the answer, so everything here is asynchronous (Node's thread
49
+ * pool): a slow disk or network mount holds up the drive, not Tabby's window.
50
+ *
51
+ * The server's paths are relative to the drive, with backslashes. Each resolves inside its folder, also once
52
+ * symbolic links are followed: a path that leaves the folder, by `..` or through a link, doesn't exist as far as
53
+ * the remote is concerned. Only regular files and folders are served (no devices, pipes or sockets, which could
54
+ * block or do things on being opened). Read-only folders refuse anything that writes.
55
+ */
56
+ class SharedDrives {
57
+ constructor(folders, log = () => null) {
58
+ this.log = log;
59
+ this.byId = new Map();
60
+ this.roots = new Map();
61
+ this.handles = new Map();
62
+ this.nextHandle = 1;
63
+ // RDPDR device ids are the session's: the audio device, when there is one, takes 0 in IronRDP.
64
+ this.drives = folders.map((folder, i) => {
65
+ this.byId.set(i + 1, folder);
66
+ return { id: i + 1, name: folder.name };
67
+ });
68
+ }
69
+ /** Closes whatever the server left open (the connection ended). */
70
+ dispose() {
71
+ for (const handle of this.handles.values()) {
72
+ handle.file.close().catch(() => null);
73
+ }
74
+ this.handles.clear();
75
+ }
76
+ /** The folder's own real path (it may be reached through a link itself, as /tmp is on macOS). */
77
+ rootOf(folder) {
78
+ let root = this.roots.get(folder);
79
+ if (!root) {
80
+ root = fsp.realpath(folder.path);
81
+ this.roots.set(folder, root);
82
+ root.catch(() => this.roots.delete(folder)); // not there now: look again next time
83
+ }
84
+ return root;
85
+ }
86
+ /**
87
+ * The place of `remote` on drive `deviceId`. `..` doesn't resolve, and neither does a path whose links lead out
88
+ * of the shared folder (ENOENT either way). With `follow`, the last component's link is followed too (reading,
89
+ * listing, opening); without, it is left alone (removing or renaming a link works on the link, not its target).
90
+ * What is there, if anything, is for the caller to find out.
91
+ */
92
+ async locate(deviceId, remote, follow) {
93
+ const folder = this.byId.get(deviceId) ?? fail('ENOENT', `no drive ${deviceId}`);
94
+ const parts = [];
95
+ for (const part of remote.split(/[\\/]+/)) {
96
+ if (part === '' || part === '.') {
97
+ continue;
98
+ }
99
+ if (part === '..' || part.includes('\0')) {
100
+ fail('ENOENT', `${remote}: not inside ${folder.name}`);
101
+ }
102
+ parts.push(part);
103
+ }
104
+ const root = await this.rootOf(folder);
105
+ if (!parts.length) {
106
+ return { folder, local: root, root: true };
107
+ }
108
+ const inside = (real) => real === root || real.startsWith(root.endsWith(path.sep) ? root : root + path.sep);
109
+ const outside = () => fail('ENOENT', `${remote}: leads out of ${folder.name}`);
110
+ // The folder it is in, links followed: there, and inside.
111
+ const parent = await fsp.realpath(path.join(root, ...parts.slice(0, -1)));
112
+ if (!inside(parent)) {
113
+ outside();
114
+ }
115
+ const local = path.join(parent, parts[parts.length - 1]);
116
+ if (!follow) {
117
+ return { folder, local, root: false };
118
+ }
119
+ try {
120
+ const real = await fsp.realpath(local);
121
+ return inside(real) ? { folder, local: real, root: real === root } : outside();
122
+ }
123
+ catch (e) {
124
+ if (e?.code !== 'ENOENT') {
125
+ throw e;
126
+ }
127
+ }
128
+ // Nothing there. A link to nothing isn't a place to make something: it would be made where the link points.
129
+ if (await fsp.lstat(local).then(() => true, () => false)) {
130
+ outside();
131
+ }
132
+ return { folder, local, root: false };
133
+ }
134
+ writable(folder, what) {
135
+ if (folder.readOnly) {
136
+ fail('EACCES', `${folder.name} is shared read-only: ${what}`);
137
+ }
138
+ }
139
+ handle(id) {
140
+ return this.handles.get(id) ?? fail('EBADF', `no open file ${id}`);
141
+ }
142
+ async stat(deviceId, remote) {
143
+ let s;
144
+ let folder;
145
+ try {
146
+ const place = await this.locate(deviceId, remote, true);
147
+ folder = place.folder;
148
+ s = await fsp.stat(place.local);
149
+ }
150
+ catch (e) {
151
+ if (e?.code === 'ENOENT' || e?.code === 'ENOTDIR') {
152
+ return null;
153
+ }
154
+ throw e;
155
+ }
156
+ // Devices, pipes and sockets aren't shown.
157
+ if (!s.isFile() && !s.isDirectory()) {
158
+ return null;
159
+ }
160
+ return {
161
+ dir: s.isDirectory(),
162
+ size: s.isDirectory() ? 0 : s.size,
163
+ created: s.birthtimeMs || s.ctimeMs,
164
+ accessed: s.atimeMs,
165
+ modified: s.mtimeMs,
166
+ readonly: folder.readOnly || (s.mode & 0o200) === 0,
167
+ };
168
+ }
169
+ async list(deviceId, remote) {
170
+ const { local } = await this.locate(deviceId, remote, true);
171
+ return fsp.readdir(local);
172
+ }
173
+ async open(deviceId, remote, mode) {
174
+ const { folder, local } = await this.locate(deviceId, remote, true);
175
+ if (mode.write || mode.create || mode.truncate) {
176
+ this.writable(folder, `can't write ${remote}`);
177
+ }
178
+ const existing = await fsp.stat(local).catch(() => null);
179
+ if (existing && !existing.isFile()) {
180
+ fail(existing.isDirectory() ? 'EISDIR' : 'EACCES', `${remote}: not a file`);
181
+ }
182
+ // The path was resolved just now: a link (or a pipe) put in its place meanwhile isn't followed (or waited on).
183
+ const { O_RDONLY, O_RDWR, O_CREAT, O_EXCL, O_TRUNC } = fs.constants;
184
+ const guard = (fs.constants.O_NOFOLLOW ?? 0) | (fs.constants.O_NONBLOCK ?? 0);
185
+ const flags = (mode.write ? O_RDWR : O_RDONLY) | (mode.create ? O_CREAT : 0) | (mode.exclusive ? O_EXCL : 0) | (mode.truncate ? O_TRUNC : 0) | guard;
186
+ const file = await fsp.open(local, flags, 0o644);
187
+ if (!(await file.stat()).isFile()) {
188
+ await file.close();
189
+ fail('EACCES', `${remote}: not a file`);
190
+ }
191
+ const id = this.nextHandle++;
192
+ this.handles.set(id, { file, folder });
193
+ return id;
194
+ }
195
+ async read(handle, offset, length) {
196
+ const { file } = this.handle(handle);
197
+ const buffer = Buffer.allocUnsafe(Math.min(length, READ_LIMIT));
198
+ let read = 0;
199
+ // A read can come back short before the end of the file: fill what was asked.
200
+ while (read < buffer.length) {
201
+ const { bytesRead } = await file.read(buffer, read, buffer.length - read, offset + read);
202
+ if (bytesRead === 0) {
203
+ break;
204
+ }
205
+ read += bytesRead;
206
+ }
207
+ return new Uint8Array(buffer.buffer, buffer.byteOffset, read);
208
+ }
209
+ async write(handle, offset, data) {
210
+ const { file, folder } = this.handle(handle);
211
+ this.writable(folder, 'can\'t write');
212
+ let written = 0;
213
+ while (written < data.length) {
214
+ written += (await file.write(data, written, data.length - written, offset + written)).bytesWritten;
215
+ }
216
+ return written;
217
+ }
218
+ async truncate(handle, size) {
219
+ const { file, folder } = this.handle(handle);
220
+ this.writable(folder, 'can\'t resize');
221
+ await file.truncate(size);
222
+ }
223
+ async close(handle) {
224
+ const entry = this.handles.get(handle);
225
+ if (entry) {
226
+ this.handles.delete(handle);
227
+ await entry.file.close();
228
+ }
229
+ }
230
+ async mkdir(deviceId, remote) {
231
+ const { folder, local } = await this.locate(deviceId, remote, true);
232
+ this.writable(folder, `can't make ${remote}`);
233
+ await fsp.mkdir(local);
234
+ }
235
+ async remove(deviceId, remote) {
236
+ const { folder, local, root } = await this.locate(deviceId, remote, false);
237
+ this.writable(folder, `can't remove ${remote}`);
238
+ if (root) {
239
+ fail('EACCES', `${folder.name}: the shared folder itself stays`);
240
+ }
241
+ // Directories only when empty, as on Windows (ENOTEMPTY otherwise); the server removes contents first. A
242
+ // link is removed itself, whatever it points to.
243
+ if ((await fsp.lstat(local)).isDirectory()) {
244
+ await fsp.rmdir(local);
245
+ }
246
+ else {
247
+ await fsp.unlink(local);
248
+ }
249
+ }
250
+ async rename(deviceId, from, to, replace) {
251
+ const source = await this.locate(deviceId, from, false);
252
+ const target = await this.locate(deviceId, to, false);
253
+ this.writable(source.folder, `can't rename ${from}`);
254
+ if (source.root || target.root) {
255
+ fail('EACCES', `${source.folder.name}: the shared folder itself stays`);
256
+ }
257
+ const moving = await fsp.lstat(source.local);
258
+ const existing = await fsp.lstat(target.local).catch(() => null);
259
+ // Something else in the way: only when asked to replace it. The same file under another spelling of its
260
+ // name (a case-insensitive file system) is a plain rename.
261
+ if (existing && !(existing.dev === moving.dev && existing.ino === moving.ino)) {
262
+ if (!replace) {
263
+ fail('EEXIST', `${to} exists`);
264
+ }
265
+ if (existing.isDirectory() || moving.isDirectory()) {
266
+ fail('EACCES', `${to}: folders aren't replaced`);
267
+ }
268
+ }
269
+ await fsp.rename(source.local, target.local);
270
+ }
271
+ async volume(deviceId) {
272
+ const folder = this.byId.get(deviceId) ?? fail('ENOENT', `no drive ${deviceId}`);
273
+ try {
274
+ const s = await fsp.statfs(await this.rootOf(folder));
275
+ return { total: s.bsize * s.blocks, free: s.bsize * s.bavail, label: folder.name };
276
+ }
277
+ catch (e) {
278
+ this.log(`drives: ${folder.name}: no volume information: ${e?.message ?? e}`);
279
+ return { total: 0, free: 0, label: folder.name };
280
+ }
281
+ }
282
+ }
283
+ exports.SharedDrives = SharedDrives;
284
+ //# sourceMappingURL=drives.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"drives.js","sourceRoot":"","sources":["../src/drives.ts"],"names":[],"mappings":"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"}
@@ -1,9 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.FileTransfer = exports.STYLE = void 0;
3
+ exports.FileTransfer = exports.STYLE = exports.DiskStorage = void 0;
4
4
  exports.clipboardPaths = clipboardPaths;
5
5
  exports.savePath = savePath;
6
6
  exports.uniquePath = uniquePath;
7
+ exports.foldersInside = foldersInside;
8
+ exports.place = place;
7
9
  const fs = require("fs");
8
10
  const os = require("os");
9
11
  const path = require("path");
@@ -100,6 +102,56 @@ function entriesFor(paths) {
100
102
  paths.forEach(p => walk(p, undefined, 0));
101
103
  return entries;
102
104
  }
105
+ /** On a download's result: the temporary file holding it (see DiskStorage). */
106
+ const DISK_PATH = Symbol('tabby-rdp download');
107
+ /**
108
+ * Where downloads are kept while they arrive (IronRDP's FileStorageBackend): a file each in a private temporary
109
+ * folder, written chunk by chunk, so a large file takes its size on disk rather than twice that in memory. The
110
+ * result handed back is an empty Blob that names the file; saveAll moves it into place.
111
+ */
112
+ class DiskStorage {
113
+ constructor() {
114
+ this.name = 'disk';
115
+ this.dir = null;
116
+ this.count = 0;
117
+ }
118
+ async createWriteHandle(_fileName, _expectedSize) {
119
+ this.dir ??= fs.promises.mkdtemp(path.join(os.tmpdir(), 'tabby-rdp-')); // 0700, a name nobody could prepare
120
+ const file = path.join(await this.dir, String(++this.count));
121
+ const handle = await fs.promises.open(file, 'wx', 0o600);
122
+ let bytesWritten = 0;
123
+ let open = true;
124
+ const close = async () => {
125
+ if (open) {
126
+ open = false;
127
+ await handle.close();
128
+ }
129
+ };
130
+ return {
131
+ get bytesWritten() { return bytesWritten; },
132
+ async write(chunk) {
133
+ await handle.appendFile(chunk);
134
+ bytesWritten += chunk.length;
135
+ },
136
+ async finalize() {
137
+ await close();
138
+ return Object.assign(new Blob([]), { [DISK_PATH]: file });
139
+ },
140
+ async abort() {
141
+ await close().catch(() => null);
142
+ await fs.promises.rm(file, { force: true });
143
+ },
144
+ };
145
+ }
146
+ async dispose() {
147
+ const dir = this.dir;
148
+ this.dir = null;
149
+ if (dir) {
150
+ await dir.then(d => fs.promises.rm(d, { recursive: true, force: true }), () => null);
151
+ }
152
+ }
153
+ }
154
+ exports.DiskStorage = DiskStorage;
103
155
  exports.STYLE = `
104
156
  .trd-toast { position: absolute; top: 12px; right: 12px; z-index: 2; max-width: min(420px, calc(100% - 24px));
105
157
  display: flex; gap: 10px; align-items: center; padding: 8px 10px; border-radius: 6px;
@@ -129,7 +181,13 @@ class FileTransfer {
129
181
  this.pendingPaste = null;
130
182
  /** Resolves when the remote has taken in the files last offered (its Format List Response): true if it accepted them. */
131
183
  this.offerTaken = null;
132
- this.provider = new rdp.RdpFileTransferProvider({ storageBackend: 'blob' });
184
+ /**
185
+ * Takes the overlay's drop listeners off again: the overlay outlives this (a reconnect makes a new FileTransfer
186
+ * on it). With removeEventListener, not an AbortSignal: zone.js keeps one native listener per event and its own
187
+ * list of handlers, and a signal removes the native one behind its back, so the next handlers never get attached.
188
+ */
189
+ this.stopListening = [];
190
+ this.provider = new rdp.RdpFileTransferProvider({ storageBackend: new DiskStorage() });
133
191
  this.provider.on('files-available', (files) => this.offer(files));
134
192
  this.provider.on('error', (e) => {
135
193
  this.log(`files: ${e?.message ?? JSON.stringify(e)}`);
@@ -137,26 +195,33 @@ class FileTransfer {
137
195
  });
138
196
  // Drop files on the desktop to send them. Handled here so Tabby doesn't paste their paths into
139
197
  // the console under the desktop.
140
- overlay.addEventListener('dragover', event => {
198
+ const listen = (type, handler, capture = false) => {
199
+ overlay.addEventListener(type, handler, capture);
200
+ this.stopListening.push(() => overlay.removeEventListener(type, handler, capture));
201
+ };
202
+ listen('dragover', event => {
141
203
  if (event.dataTransfer?.types.includes('Files')) {
142
204
  event.preventDefault();
143
205
  event.stopPropagation();
144
206
  overlay.classList.add('trd-drop-target');
145
207
  }
146
208
  });
147
- overlay.addEventListener('dragleave', event => {
209
+ listen('dragleave', event => {
148
210
  if (!overlay.contains(event.relatedTarget)) {
149
211
  overlay.classList.remove('trd-drop-target');
150
212
  }
151
213
  });
152
- overlay.addEventListener('drop', event => {
214
+ listen('drop', event => {
153
215
  overlay.classList.remove('trd-drop-target');
154
216
  if (!event.dataTransfer?.types.includes('Files')) {
155
217
  return;
156
218
  }
157
219
  event.preventDefault();
158
220
  event.stopPropagation();
159
- this.provider.handleDrop(event).then((dropped) => this.send(dropped), (e) => this.toast(`Couldn't read the dropped files: ${e?.message ?? e}`));
221
+ this.provider.handleDrop(event).then((dropped) => this.send(dropped), (e) => {
222
+ this.log(`files: drop: ${e?.message ?? e}`);
223
+ this.toast(`Couldn't read the dropped files: ${e?.message ?? e}`);
224
+ });
160
225
  }, true);
161
226
  }
162
227
  /** Mac → remote: offers the files on the remote clipboard; the remote copies them when they're pasted. */
@@ -180,6 +245,7 @@ class FileTransfer {
180
245
  return upload.completion.then(() => { this.log('files: pasted on the remote'); this.toast(`Copied ${plural(count, 'file')} to the remote desktop`); }, (e) => { this.log(`files: upload: ${e?.message ?? e}`); this.toast(`Copying to the remote desktop failed: ${e?.message ?? e}`); });
181
246
  }
182
247
  catch (e) {
248
+ this.log(`files: not offered: ${e?.message ?? e}`);
183
249
  this.toast(`Couldn't offer the files: ${e?.message ?? e}`);
184
250
  return null;
185
251
  }
@@ -262,11 +328,9 @@ class FileTransfer {
262
328
  if (file.isDirectory) {
263
329
  continue;
264
330
  }
265
- const target = uniquePath(savePath(dir, file.path, file.name));
331
+ const wanted = savePath(dir, file.path, file.name);
266
332
  const blob = await this.provider.downloadFile(file, index).completion;
267
- await fs.promises.mkdir(path.dirname(target), { recursive: true });
268
- await fs.promises.writeFile(target, Buffer.from(await blob.arrayBuffer()));
269
- saved.push(target);
333
+ saved.push(await place(dir, wanted, blob));
270
334
  }
271
335
  this.lastSaved = saved;
272
336
  this.log(`files: saved ${saved.length}`);
@@ -310,6 +374,8 @@ class FileTransfer {
310
374
  }
311
375
  }
312
376
  dispose() {
377
+ this.stopListening.splice(0).forEach(stop => stop());
378
+ this.overlay.classList.remove('trd-drop-target');
313
379
  clearTimeout(this.toastTimer);
314
380
  this.toastEl?.remove();
315
381
  try {
@@ -337,7 +403,7 @@ function savePath(dir, folder, name) {
337
403
  }
338
404
  return target;
339
405
  }
340
- /** `file.txt`, or `file 2.txt`, `file 3.txt`… if taken. */
406
+ /** `file.txt`, or `file 2.txt`, `file 3.txt`… if taken. For names of the plugin's own choosing (screenshots). */
341
407
  function uniquePath(target) {
342
408
  if (!fs.existsSync(target)) {
343
409
  return target;
@@ -350,4 +416,55 @@ function uniquePath(target) {
350
416
  }
351
417
  }
352
418
  }
419
+ /**
420
+ * Makes the folders from `dir` down to `folder` (which savePath put under it), one at a time, and returns the
421
+ * folder's real path. An existing component may be a link only if it stays inside `dir`: folder names come from the
422
+ * remote, and a link in Downloads with a matching name would otherwise take the files wherever it points.
423
+ */
424
+ async function foldersInside(dir, folder) {
425
+ await fs.promises.mkdir(dir, { recursive: true });
426
+ const root = await fs.promises.realpath(dir);
427
+ let at = root;
428
+ for (const part of path.relative(path.resolve(dir), folder).split(path.sep).filter(Boolean)) {
429
+ const next = path.join(at, part);
430
+ await fs.promises.mkdir(next).catch((e) => {
431
+ if (e?.code !== 'EEXIST') {
432
+ throw e;
433
+ }
434
+ });
435
+ at = await fs.promises.realpath(next);
436
+ if (at !== root && !at.startsWith(root + path.sep)) {
437
+ throw new Error(`refused to save through a link that leaves the folder: ${JSON.stringify(part)}`);
438
+ }
439
+ }
440
+ return at;
441
+ }
442
+ /**
443
+ * Puts a download at `wanted` under `dir` (or `name 2.ext`, `name 3.ext`… when taken) and returns where it went.
444
+ * The file is created there and never replaces or follows what exists: a name can be taken, or made a link,
445
+ * between looking and writing.
446
+ */
447
+ async function place(dir, wanted, blob) {
448
+ const folder = await foldersInside(dir, path.dirname(wanted));
449
+ const { name, ext } = path.parse(wanted);
450
+ const temporary = blob[DISK_PATH];
451
+ for (let n = 1;; n++) {
452
+ const target = path.join(folder, n === 1 ? `${name}${ext}` : `${name} ${n}${ext}`);
453
+ try {
454
+ if (temporary) {
455
+ await fs.promises.copyFile(temporary, target, fs.constants.COPYFILE_EXCL);
456
+ await fs.promises.rm(temporary, { force: true });
457
+ }
458
+ else {
459
+ await fs.promises.writeFile(target, Buffer.from(await blob.arrayBuffer()), { flag: 'wx' });
460
+ }
461
+ return target;
462
+ }
463
+ catch (e) {
464
+ if (e?.code !== 'EEXIST' || n >= 10000) {
465
+ throw e;
466
+ }
467
+ }
468
+ }
469
+ }
353
470
  //# sourceMappingURL=fileTransfer.js.map