Implemented the host name auto suggestion, and added Preset feature

This commit is contained in:
NI
2020-02-07 18:05:44 +08:00
parent 0a930d1345
commit 67c99e3092
22 changed files with 1582 additions and 332 deletions

View File

@@ -15,8 +15,9 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import Exception from "./exception.js";
import * as subscribe from "../stream/subscribe.js";
import Exception from "./exception.js";
import * as presets from "./presets.js";
export const NEXT_PROMPT = 1;
export const NEXT_WAIT = 2;
@@ -131,8 +132,12 @@ const defField = {
type: "",
value: "",
example: "",
readonly: false,
suggestions(input) {
return [];
},
verify(v) {
return "OK";
return "";
}
};
@@ -155,19 +160,21 @@ export function field(def, f) {
}
for (let i in f) {
if (typeof n[i] !== typeof f[i]) {
throw new Exception(
'Field data type for "' +
i +
'" was not unmatched. Expecting "' +
typeof def[i] +
'", got "' +
typeof f[i] +
'" instead'
);
if (typeof n[i] === typeof f[i]) {
n[i] = f[i];
continue;
}
n[i] = f[i];
throw new Exception(
'Field data type for "' +
i +
'" was unmatched. Expecting "' +
typeof n[i] +
'", got "' +
typeof f[i] +
'" instead'
);
}
if (!n["name"]) {
@@ -206,6 +213,31 @@ export function fields(definitions, fs) {
return fss;
}
/**
* Build command fields with preset data
*
* @param {object} definitions Definition of a group of fields
* @param {object} fieldsData field data object, formated like a `defField`
* @param {presets.Preset} presetData Preset data
*
* @returns {object}
*
*/
export function fieldsWithPreset(definitions, fieldsData, presetData) {
let newFields = fields(definitions, fieldsData);
for (let i in newFields) {
try {
newFields[i].value = presetData.meta(newFields[i].name);
newFields[i].readonly = true;
} catch (e) {
// Do nothing
}
}
return newFields;
}
class Prompt {
/**
* constructor
@@ -457,7 +489,7 @@ class Wizard {
/**
* constructor
*
* @param {function} builder Command builder
* @param {object} built Command executer
* @param {subscribe.Subscribe} subs Wizard step subscriber
* @param {function} done Callback which will be called when the wizard
* is done
@@ -468,6 +500,8 @@ class Wizard {
this.subs = subs;
this.done = done;
this.closed = false;
this.built.run();
}
/**
@@ -583,8 +617,14 @@ class Builder {
*/
constructor(command) {
this.cid = command.id();
this.builder = (n, i, r, u, y, x, l) => {
return command.builder(n, i, r, u, y, x, l);
this.represeter = n => {
return command.represet(n);
};
this.wizarder = (n, i, r, u, y, x, l) => {
return command.wizard(n, i, r, u, y, x, l);
};
this.executer = (n, i, r, u, y, x, l) => {
return command.execute(n, i, r, u, y, x, l);
};
this.launchCmd = (n, i, r, u, y, x) => {
return command.launch(n, i, r, u, y, x);
@@ -638,7 +678,38 @@ class Builder {
}
/**
* Build command wizard
* Execute an automatic command wizard
*
* @param {stream.Streams} streams
* @param {controls.Controls} controls
* @param {history.History} history
* @param {presets.Preset} preset
* @param {object} session
* @param {function} done Callback which will be called when wizard is done
*
* @returns {Wizard} Command wizard
*
*/
wizard(streams, controls, history, preset, session, done) {
let subs = new subscribe.Subscribe();
return new Wizard(
this.wizarder(
new Info(this),
preset,
session,
streams,
subs,
controls,
history
),
subs,
done
);
}
/**
* Execute an automatic command wizard
*
* @param {stream.Streams} streams
* @param {controls.Controls} controls
@@ -650,11 +721,11 @@ class Builder {
* @returns {Wizard} Command wizard
*
*/
build(streams, controls, history, config, session, done) {
execute(streams, controls, history, config, session, done) {
let subs = new subscribe.Subscribe();
return new Wizard(
this.builder(
this.executer(
new Info(this),
config,
session,
@@ -707,19 +778,44 @@ class Builder {
launcher(config) {
return this.name() + ":" + encodeURI(this.launcherCmd(config));
}
/**
* Reconfigure the preset data for the command wizard
*
* @param {presets.Preset} n preset
*
* @return {presets.Preset} modified new preset
*/
represet(n) {
return this.represeter(n);
}
}
export class Preset {
/**
* constructor
*
* @param {presets.Preset} preset preset
* @param {Builder} command executor
*
*/
constructor(preset, command) {
this.preset = preset;
this.command = command;
}
}
export class Commands {
/**
* constructor
*
* @param {[]object} commands Command array
* @param {Array<object>} commands Command array
*
*/
constructor(commands) {
this.commands = [];
for (let i in commands) {
for (let i = 0; i < commands.length; i++) {
this.commands.push(new Builder(commands[i]));
}
}
@@ -727,7 +823,7 @@ export class Commands {
/**
* Return all commands
*
* @returns {[]Builder} A group of command
* @returns {Array<Builder>} A group of command
*
*/
all() {
@@ -745,4 +841,28 @@ export class Commands {
select(id) {
return this.commands[id];
}
/**
* Returns presets with merged command
*
* @param {presets.Presets} ps
*
* @returns {Array<Preset>}
*
*/
mergePresets(ps) {
let pp = [];
for (let i = 0; i < this.commands.length; i++) {
const fetched = ps.fetch(this.commands[i].name());
for (let j = 0; j < fetched.length; j++) {
pp.push(
new Preset(this.commands[i].represet(fetched[j]), this.commands[i])
);
}
}
return pp;
}
}

View File

@@ -17,6 +17,16 @@
import * as command from "./commands.js";
function metaContains(data, metaName, valContains) {
switch (typeof data[metaName]) {
case "string":
return data[metaName].indexOf(valContains) >= 0;
default:
return false;
}
}
export class History {
/**
* constructor
@@ -206,4 +216,39 @@ export class History {
this.store();
}
/**
* Search for partly matched results
*
* @param {string} type of the history record
* @param {string} metaName name of the meta data
* @param {string} keyword keyword to search
* @param {number} max max results
*/
search(type, metaName, keyword, max) {
let maxResults = max > this.records.length ? this.records.length : max;
let s = [];
if (maxResults < 0) {
maxResults = this.records.length;
}
for (let i = 0; i < this.records.length && s.length < maxResults; i++) {
if (this.records[i].type !== type) {
continue;
}
if (!this.records[i].data) {
continue;
}
if (!metaContains(this.records[i].data, metaName, keyword)) {
continue;
}
s.push(this.records[i]);
}
return s;
}
}

292
ui/commands/presets.js Normal file
View File

@@ -0,0 +1,292 @@
// Sshwifty - A Web SSH client
//
// Copyright (C) 2019-2020 Rui NI <nirui@gmx.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import Exception from "./exception.js";
/**
* Default preset item, contains data of a default preset
*
*/
const presetItem = {
title: "",
type: "",
host: "",
meta: {}
};
/**
* Verify Preset Item Meta
*
* @param {object} preset
*
*/
function verifyPresetItemMeta(preset) {
for (let i in preset.meta) {
if (typeof preset.meta[i] === "string") {
continue;
}
throw new Exception(
'The data type of meta field "' +
i +
'" was "' +
typeof preset.meta[i] +
'" instead of expected "string"'
);
}
}
/**
* Parse and verify the given preset, return a valid preset
*
* @param {object} item
*
* @throws {Exception} when invalid data is given
*
* @return {object}
*
*/
function parsePresetItem(item) {
let preset = {};
for (let i in presetItem) {
preset[i] = presetItem[i];
}
for (let i in presetItem) {
if (typeof presetItem[i] === typeof item[i]) {
preset[i] = item[i];
continue;
}
throw new Exception(
'Expecting the data type of "' +
i +
'" is "' +
typeof presetItem[i] +
'", given "' +
typeof item[i] +
'" instead'
);
}
verifyPresetItemMeta(preset.meta);
return preset;
}
/**
* Preset data
*
*/
export class Preset {
/**
* constructor
*
* @param {object} preset preset data
*
*/
constructor(preset) {
this.preset = parsePresetItem(preset);
}
/**
* Return the title of the preset
*
* @returns {string}
*
*/
title() {
return this.preset.title;
}
/**
* Return the type of the preset
*
* @returns {string}
*
*/
type() {
return this.preset.type;
}
/**
* Return the host of the preset
*
* @returns {string}
*
*/
host() {
return this.preset.host;
}
/**
* Return the given meta of current preset
*
* @param {string} name name of the meta data
*
* @throws {Exception} when invalid data is given
*
* @returns {string}
*
*/
meta(name) {
if (typeof this.preset.meta[name] !== "string") {
throw new Exception('Meta "' + name + '" was undefined');
}
return this.preset.meta[name];
}
/**
* Insert new meta item
*
* @param {string} name name of the meta data
* @param {string} data data of the meta data
*
* @throws {Exception} when invalid data is given
*
*/
insertMeta(name, data) {
if (typeof this.preset.meta[name] !== "undefined") {
throw new Exception('Meta "' + name + '" has already been defined');
}
this.preset.meta[name] = data;
}
}
/**
* Returns an empty preset
*
* @returns {Preset}
*
*/
export function emptyPreset() {
return new Preset({
title: "Default",
type: "Default",
host: "",
meta: {}
});
}
/**
* Command Preset manager
*
*/
export class Presets {
/**
* constructor
*
* @param {Array<object>} presets Array of preset data
*
*/
constructor(presets) {
this.presets = [];
for (let i = 0; i < presets.length; i++) {
this.presets.push(new Preset(presets[i]));
}
}
/**
* Return all presets of a type
*
* @param {string} type type of the presets data
*
* @returns {Array<Preset>}
*
*/
fetch(type) {
let presets = [];
for (let i = 0; i < this.presets.length; i++) {
if (this.presets[i].type() !== type) {
continue;
}
presets.push(this.presets[i]);
}
return presets;
}
/**
* Return presets with matched type and meta data
*
* @param {string} type type of the presets data
* @param {string} metaName name of the meta data
* @param {string} metaVal value of the meta data
*
* @returns {Array<Preset>}
*
*/
meta(type, metaName, metaVal) {
let presets = [];
for (let i = 0; i < this.presets.length; i++) {
if (this.presets[i].type() !== type) {
continue;
}
try {
if (this.presets[i].meta(metaName) !== metaVal) {
continue;
}
} catch (e) {
if (!(e instanceof Exception)) {
throw e;
}
continue;
}
presets.push(this.presets[i]);
}
return presets;
}
/**
* Return presets with matched type and host
*
* @param {string} type type of the presets
* @param {string} host host of the presets
*
* @returns {Array<Preset>}
*
*/
hosts(type, host) {
let presets = [];
for (let i = 0; i < this.presets.length; i++) {
if (this.presets[i].type() !== type) {
continue;
}
if (this.presets[i].host() !== host) {
continue;
}
presets.push(this.presets[i]);
}
return presets;
}
}

View File

@@ -15,17 +15,18 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import * as header from "../stream/header.js";
import * as reader from "../stream/reader.js";
import * as stream from "../stream/stream.js";
import * as address from "./address.js";
import * as command from "./commands.js";
import * as common from "./common.js";
import * as event from "./events.js";
import * as reader from "../stream/reader.js";
import * as stream from "../stream/stream.js";
import * as controls from "./controls.js";
import * as header from "../stream/header.js";
import * as history from "./history.js";
import * as strings from "./string.js";
import * as event from "./events.js";
import Exception from "./exception.js";
import * as history from "./history.js";
import * as presets from "./presets.js";
import * as strings from "./string.js";
const AUTHMETHOD_NONE = 0x00;
const AUTHMETHOD_PASSPHRASE = 0x01;
@@ -57,6 +58,8 @@ const FingerprintPromptVerifyPassed = 0x00;
const FingerprintPromptVerifyNoRecord = 0x01;
const FingerprintPromptVerifyMismatch = 0x02;
const HostMaxSearchResults = 3;
class SSH {
/**
* constructor
@@ -245,6 +248,10 @@ const initialFieldDef = {
type: "text",
value: "",
example: "guest",
readonly: false,
suggestions(input) {
return [];
},
verify(d) {
if (d.length <= 0) {
throw new Error("Username must be specified");
@@ -265,6 +272,10 @@ const initialFieldDef = {
type: "text",
value: "",
example: "ssh.vaguly.com:22",
readonly: false,
suggestions(input) {
return [];
},
verify(d) {
if (d.length <= 0) {
throw new Error("Hostname must be specified");
@@ -295,6 +306,10 @@ const initialFieldDef = {
type: "select",
value: "utf-8",
example: common.charsetPresets.join(","),
readonly: false,
suggestions(input) {
return [];
},
verify(d) {
for (let i in common.charsetPresets) {
if (common.charsetPresets[i] !== d) {
@@ -315,6 +330,10 @@ const initialFieldDef = {
"SSH session is handled by the backend. Traffic will be decrypted " +
"on the backend server and then transmit back to your client.",
example: "",
readonly: false,
suggestions(input) {
return [];
},
verify(d) {
return "";
}
@@ -325,6 +344,10 @@ const initialFieldDef = {
type: "password",
value: "",
example: "----------",
readonly: false,
suggestions(input) {
return [];
},
verify(d) {
if (d.length <= 0) {
throw new Error("Password must be specified");
@@ -356,6 +379,10 @@ const initialFieldDef = {
type: "textfile",
value: "",
example: "",
readonly: false,
suggestions(input) {
return [];
},
verify(d) {
if (d.length <= 0) {
throw new Error("Private Key must be specified");
@@ -410,6 +437,10 @@ const initialFieldDef = {
type: "radio",
value: "",
example: "Password,Private Key,None",
readonly: false,
suggestions(input) {
return [];
},
verify(d) {
switch (d) {
case "Password":
@@ -431,6 +462,10 @@ const initialFieldDef = {
type: "textdata",
value: "",
example: "",
readonly: false,
suggestions(input) {
return [];
},
verify(d) {
return "";
}
@@ -468,7 +503,7 @@ class Wizard {
* constructor
*
* @param {command.Info} info
* @param {object} config
* @param {presets.Preset} preset
* @param {object} session
* @param {streams.Streams} streams
* @param {subscribe.Subscribe} subs
@@ -476,11 +511,11 @@ class Wizard {
* @param {history.History} history
*
*/
constructor(info, config, session, streams, subs, controls, history) {
constructor(info, preset, session, streams, subs, controls, history) {
this.info = info;
this.preset = preset;
this.hasStarted = false;
this.streams = streams;
this.config = config;
this.session = session
? session
: {
@@ -489,7 +524,9 @@ class Wizard {
this.step = subs;
this.controls = controls.get("SSH");
this.history = history;
}
run() {
this.step.resolve(this.stepInitialPrompt());
}
@@ -680,26 +717,6 @@ class Wizard {
stepInitialPrompt() {
let self = this;
if (this.config) {
self.hasStarted = true;
self.streams.request(COMMAND_ID, sd => {
return self.buildCommand(
sd,
{
user: this.config.user,
authentication: this.config.authentication,
host: this.config.host,
charset: this.config.charset ? this.config.charset : "utf-8",
fingerprint: this.config.fingerprint
},
this.session
);
});
return self.stepWaitForAcceptWait();
}
return command.prompt(
"SSH",
"Secure Shell Host",
@@ -717,26 +734,57 @@ class Wizard {
charset: r.encoding,
fingerprint: ""
},
this.session
self.session
);
});
self.step.resolve(self.stepWaitForAcceptWait());
},
() => {},
command.fields(initialFieldDef, [
{ name: "User" },
{ name: "Host" },
{ name: "Authentication" },
{ name: "Encoding" },
{ name: "Notice" }
])
command.fieldsWithPreset(
initialFieldDef,
[
{ name: "User" },
{
name: "Host",
suggestions(input) {
const hosts = self.history.search(
"SSH",
"host",
input,
HostMaxSearchResults
);
let sugg = [];
for (let i = 0; i < hosts.length; i++) {
sugg.push({
title: hosts[i].title,
value: hosts[i].data.host,
meta: {
User: hosts[i].data.user,
Authentication: hosts[i].data.authentication,
Encoding: hosts[i].data.charset
}
});
}
return sugg;
}
},
{ name: "Authentication" },
{ name: "Encoding" },
{ name: "Notice" }
],
self.preset
)
);
}
async stepFingerprintPrompt(rd, sd, verify, newFingerprint) {
let self = this,
fingerprintData = new TextDecoder("utf-8").decode(
const self = this;
let fingerprintData = new TextDecoder("utf-8").decode(
await reader.readCompletely(rd)
),
fingerprintChanged = false;
@@ -745,7 +793,7 @@ class Wizard {
case FingerprintPromptVerifyPassed:
sd.send(CLIENT_CONNECT_RESPOND_FINGERPRINT, new Uint8Array([0]));
return this.stepContinueWaitForEstablishWait();
return self.stepContinueWaitForEstablishWait();
case FingerprintPromptVerifyMismatch:
fingerprintChanged = true;
@@ -783,8 +831,9 @@ class Wizard {
}
async stepCredentialPrompt(rd, sd, config, newCredential) {
let self = this,
fields = [];
const self = this;
let fields = [];
if (config.credential.length > 0) {
sd.send(
@@ -792,7 +841,7 @@ class Wizard {
new TextEncoder().encode(config.credential)
);
return this.stepContinueWaitForEstablishWait();
return self.stepContinueWaitForEstablishWait();
}
switch (config.auth) {
@@ -836,11 +885,61 @@ class Wizard {
)
);
},
command.fields(initialFieldDef, fields)
command.fieldsWithPreset(initialFieldDef, fields, self.preset)
);
}
}
class Executer extends Wizard {
/**
* constructor
*
* @param {command.Info} info
* @param {config} config
* @param {object} session
* @param {streams.Streams} streams
* @param {subscribe.Subscribe} subs
* @param {controls.Controls} controls
* @param {history.History} history
*
*/
constructor(info, config, session, streams, subs, controls, history) {
super(
info,
presets.emptyPreset(),
session,
streams,
subs,
controls,
history
);
this.config = config;
}
stepInitialPrompt() {
const self = this;
self.hasStarted = true;
self.streams.request(COMMAND_ID, sd => {
return self.buildCommand(
sd,
{
user: self.config.user,
authentication: self.config.authentication,
host: self.config.host,
charset: self.config.charset ? self.config.charset : "utf-8",
fingerprint: self.config.fingerprint
},
self.session
);
});
return self.stepWaitForAcceptWait();
}
}
export class Command {
constructor() {}
@@ -860,8 +959,20 @@ export class Command {
return "#3c8";
}
builder(info, config, session, streams, subs, controls, history) {
return new Wizard(info, config, session, streams, subs, controls, history);
wizard(info, preset, session, streams, subs, controls, history) {
return new Wizard(info, preset, session, streams, subs, controls, history);
}
execute(info, config, session, streams, subs, controls, history) {
return new Executer(
info,
config,
session,
streams,
subs,
controls,
history
);
}
launch(info, launcher, streams, subs, controls, history) {
@@ -893,7 +1004,7 @@ export class Command {
);
}
return this.builder(
return this.execute(
info,
{
user: user,
@@ -920,4 +1031,14 @@ export class Command {
(config.charset ? config.charset : "utf-8")
);
}
represet(preset) {
const host = preset.host();
if (host.length > 0) {
preset.insertMeta("Host", host);
}
return preset;
}
}

View File

@@ -15,16 +15,17 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import * as header from "../stream/header.js";
import * as reader from "../stream/reader.js";
import * as stream from "../stream/stream.js";
import * as address from "./address.js";
import * as command from "./commands.js";
import * as common from "./common.js";
import * as event from "./events.js";
import * as reader from "../stream/reader.js";
import * as stream from "../stream/stream.js";
import * as controls from "./controls.js";
import * as history from "./history.js";
import * as header from "../stream/header.js";
import * as event from "./events.js";
import Exception from "./exception.js";
import * as history from "./history.js";
import * as presets from "./presets.js";
const COMMAND_ID = 0x00;
@@ -36,6 +37,8 @@ const SERVER_DIAL_CONNECTED = 0x02;
const DEFAULT_PORT = 23;
const HostMaxSearchResults = 3;
class Telnet {
/**
* constructor
@@ -185,6 +188,10 @@ const initialFieldDef = {
type: "text",
value: "",
example: "telnet.vaguly.com:23",
readonly: false,
suggestions(input) {
return [];
},
verify(d) {
if (d.length <= 0) {
throw new Error("Hostname must be specified");
@@ -215,6 +222,10 @@ const initialFieldDef = {
type: "select",
value: "utf-8",
example: common.charsetPresets.join(","),
readonly: false,
suggestions(input) {
return [];
},
verify(d) {
for (let i in common.charsetPresets) {
if (common.charsetPresets[i] !== d) {
@@ -234,7 +245,7 @@ class Wizard {
* constructor
*
* @param {command.Info} info
* @param {object} config
* @param {presets.Preset} preset
* @param {object} session
* @param {streams.Streams} streams
* @param {subscribe.Subscribe} subs
@@ -242,16 +253,18 @@ class Wizard {
* @param {history.History} history
*
*/
constructor(info, config, session, streams, subs, controls, history) {
constructor(info, preset, session, streams, subs, controls, history) {
this.info = info;
this.preset = preset;
this.hasStarted = false;
this.streams = streams;
this.config = config;
this.session = session;
this.step = subs;
this.controls = controls.get("Telnet");
this.history = history;
}
run() {
this.step.resolve(this.stepInitialPrompt());
}
@@ -378,24 +391,7 @@ class Wizard {
}
stepInitialPrompt() {
let self = this;
if (this.config) {
self.hasStarted = true;
self.streams.request(COMMAND_ID, sd => {
return self.buildCommand(
sd,
{
host: this.config.host,
charset: this.config.charset ? this.config.charset : "utf-8"
},
this.session
);
});
return self.stepWaitForAcceptWait();
}
const self = this;
return command.prompt(
"Telnet",
@@ -411,18 +407,96 @@ class Wizard {
host: r.host,
charset: r.encoding
},
this.session
self.session
);
});
self.step.resolve(self.stepWaitForAcceptWait());
},
() => {},
command.fields(initialFieldDef, [{ name: "Host" }, { name: "Encoding" }])
command.fieldsWithPreset(
initialFieldDef,
[
{
name: "Host",
suggestions(input) {
const hosts = self.history.search(
"Telnet",
"host",
input,
HostMaxSearchResults
);
let sugg = [];
for (let i = 0; i < hosts.length; i++) {
sugg.push({
title: hosts[i].title,
value: hosts[i].data.host,
meta: {
Encoding: hosts[i].data.charset
}
});
}
return sugg;
}
},
{ name: "Encoding" }
],
self.preset
)
);
}
}
class Executor extends Wizard {
/**
* constructor
*
* @param {command.Info} info
* @param {object} config
* @param {object} session
* @param {streams.Streams} streams
* @param {subscribe.Subscribe} subs
* @param {controls.Controls} controls
* @param {history.History} history
*
*/
constructor(info, config, session, streams, subs, controls, history) {
super(
info,
presets.emptyPreset(),
session,
streams,
subs,
controls,
history
);
this.config = config;
}
stepInitialPrompt() {
const self = this;
self.hasStarted = true;
self.streams.request(COMMAND_ID, sd => {
return self.buildCommand(
sd,
{
host: self.config.host,
charset: self.config.charset ? self.config.charset : "utf-8"
},
self.session
);
});
return self.stepWaitForAcceptWait();
}
}
export class Command {
constructor() {}
@@ -442,8 +516,20 @@ export class Command {
return "#6ac";
}
builder(info, config, session, streams, subs, controls, history) {
return new Wizard(info, config, session, streams, subs, controls, history);
wizard(info, preset, session, streams, subs, controls, history) {
return new Wizard(info, preset, session, streams, subs, controls, history);
}
execute(info, config, session, streams, subs, controls, history) {
return new Executor(
info,
config,
session,
streams,
subs,
controls,
history
);
}
launch(info, launcher, streams, subs, controls, history) {
@@ -476,7 +562,7 @@ export class Command {
}
}
return this.builder(
return this.execute(
info,
{
host: d[0],
@@ -493,4 +579,14 @@ export class Command {
launcher(config) {
return config.host + "|" + (config.charset ? config.charset : "utf-8");
}
represet(preset) {
const host = preset.host();
if (host.length > 0) {
preset.insertMeta("Host", host);
}
return preset;
}
}