Skip to content

Commit

Permalink
Change: big rework of the system and namings
Browse files Browse the repository at this point in the history
  • Loading branch information
NobleMajo committed May 11, 2024
1 parent 9134316 commit f5752f8
Show file tree
Hide file tree
Showing 16 changed files with 787 additions and 223 deletions.
3 changes: 1 addition & 2 deletions .github/workflows/npm-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ jobs:

strategy:
matrix:
node-version: [14.x, 16.x, 18.x, 20.x, 22.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
node-version: [20.x]

steps:
- uses: actions/checkout@v4
Expand Down
27 changes: 20 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,10 @@
},
"license": "MIT",
"devDependencies": {
"typescript": "^5.4.3",
"@types/node": "^20.12.7",
"@types/ssh2": "^1.15.0",
"nodemon": "^3.1.0"
"nodemon": "^3.1.0",
"typescript": "^5.4.3"
},
"dependencies": {
"ssh2": "^1.15.0"
Expand Down
3 changes: 2 additions & 1 deletion src/HostHop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ export function createClient(
const hopClient = new SshClient()

hopClient.on("ready", () => res(hopClient))
hopClient.on("error", rej)
hopClient.on("error", (err) => rej(err))

hopClient.connect({
...settings,
host: settings.sock ? undefined : settings.host,
Expand Down
92 changes: 92 additions & 0 deletions src/HostId.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
export type HostId = `${string}@${string}`
export function parseHostId(
settings: {
host: string,
user?: string,
port?: number,
}
): HostId {
const value =
(settings.user ?? "root") +
"@" +
settings.host +
":" +
(settings.port ?? 22)

if (!isHostId(value)) {
throw new Error("Value '" + value + "' is not a valid host id")
}

return value
}

export const allowedRuleChars =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-.:"

export const allowedIdRuleChars =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789@"

export function throwCharsetError(
value: string,
chatSet: string,
): void | never {
for (const char of value) {
if (!chatSet.includes(char)) {
throw new Error("Illegal character in selector value: '" + char + "'")
}
}
}

export function matchesCharset(
value: string,
chatSet: string,
): boolean {
for (const char of value) {
if (!chatSet.includes(char)) {
return false
}
}

return true
}

export function isHostId(
value: any
): value is HostId {
if (typeof value !== "string") {
return false
}

if (!value.includes("@")) {
return false
}

const parts = value.split("@")
if (
parts.length != 2 ||
parts[0].length == 0 ||
parts[1].length == 0
) {
return false
}

if (
!matchesCharset(
parts[0],
allowedRuleChars
)
) {
return false
}

if (
!matchesCharset(
parts[1],
allowedRuleChars + ".:"
)
) {
return false
}

return true
}
90 changes: 80 additions & 10 deletions src/SshExec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,13 @@ export function toSshChannel(
channel: ClientChannel,
extras: SshChannelExtras,
): SshChannel {
for (const key of Object.keys(extras) as (keyof SshChannelExtras)[]) {
(channel as any)[key] = extras[key]
const sshChannel = channel as any

for (const key of Object.typedKeys(extras)) {
sshChannel[key] = extras[key]
}

return channel as any
return sshChannel
}

export async function execSshChannel(
Expand All @@ -95,7 +97,10 @@ export async function execSshChannel(
(res, rej) => sshClient.exec(
cmd,
settings,
(err, channel) => err ? rej(err) : res(channel)
(err, channel) =>
err ?
rej(err) :
res(channel)
)
)

Expand All @@ -116,17 +121,23 @@ export async function execSshChannel(
typeof settings.timeoutMillis == "number" &&
settings.timeoutMillis > 0
) {

channel.timeout = setTimeout(
() => {
if (!channel.closed) {
channel.close("Timeout", settings.timeoutMillis)
channel.close(
"Timeout",
settings.timeoutMillis
)
}
},
settings.timeoutMillis
)
channel.once(
"close",
() => clearTimeout(channel.timeout)
() => clearTimeout(
channel.timeout
)
)
}

Expand Down Expand Up @@ -193,14 +204,46 @@ export const defaultChannelToPromiseSettings: ChannelToPromiseSettings = {
expectedExitCode: [0],
}

export function checkDataChunk(chunk: any): string {
if (chunk instanceof Buffer) {
return chunk.toString("utf8")
} else if (typeof chunk != "string") {
throw new Error(
"Unexpected chunk type: '" + typeof chunk + "'\n" +
"Value: '" + chunk + "'"
)
}
return chunk
}

export function sshChannelToPromise(
channel: ClientChannel,
cmd: string,
): SshChannelToPromise {
return (
options?: ChannelToPromiseOptions
) => new Promise<SshChannelExit>((res, rej) => {
) => {
let resolved: boolean = false
let resolveValue: SshChannelExit | PromiseLike<SshChannelExit>

let rejected: boolean = false
let rejectReason: any

let res: (value: SshChannelExit | PromiseLike<SshChannelExit>) => void = (value) => {
if (rejected || resolved) {
return
}
resolved = true
resolveValue = value
}
let rej: (reason?: any) => void = (reason) => {
if (rejected || resolved) {
return
}
rejected = true
rejectReason = reason
}

const settings: ChannelToPromiseSettings = {
...defaultChannelToPromiseSettings,
...options,
Expand Down Expand Up @@ -262,45 +305,51 @@ export function sshChannelToPromise(
if (settings.mapStdOut) {
const mapStdOut = settings.mapStdOut
stdout.on("data", (chunk) => {
chunk = checkDataChunk(chunk)
chunk = mapStdOut(chunk, false)
if (typeof chunk == "string") {
chunks.push([false, "" + chunk])
}
})
} else {
stdout.on("data", (chunk) => {
chunk = checkDataChunk(chunk)
chunks.push([false, "" + chunk])
})
}

if (settings.mapErrOut) {
const mapErrOut = settings.mapErrOut
stderr.on("data", (chunk) => {
chunk = checkDataChunk(chunk)
chunk = mapErrOut(chunk, true)
if (typeof chunk == "string") {
chunks.push([true, "" + chunk])
}
})
} else {
stderr.on("data", (chunk) => {
chunk = checkDataChunk(chunk)
chunks.push([true, "" + chunk])
})
}
} else {
stdout.on("data", (chunk) => {
chunk = checkDataChunk(chunk)
chunks.push([false, "" + chunk])
})

stderr.on("data", (chunk) => {
chunk = checkDataChunk(chunk)
chunks.push([true, "" + chunk])
})
}

stdout.once("data", () => {
stdout.on("data", () => {
anyStd = true
})

stderr.once("data", () => {
stderr.on("data", () => {
anyErr = true
})

Expand All @@ -314,6 +363,11 @@ export function sshChannelToPromise(
}
)

channel.once(
"close",
() => rej(new Error("Ssh channel closed, check why the socket was closed or lost connection"))
)

channel.once("exit", (
code: number | null,
signal?: string,
Expand Down Expand Up @@ -368,5 +422,21 @@ export function sshChannelToPromise(

res(exit)
})
})


return new Promise<SshChannelExit>(
(res2, rej2) => {
if (resolved) {
res2(resolveValue)
return
} else if (rejected) {
rej2(rejectReason)
return
}

res = res2
rej = rej2
}
)
}
}
Loading

0 comments on commit f5752f8

Please sign in to comment.