|
|
|
|
@@ -2,47 +2,50 @@
|
|
|
|
|
* We cannot make the generated code match this API exactly because uniffi
|
|
|
|
|
* - Doesn't support ts literals for rust enums
|
|
|
|
|
* - Doesn't support passing a js object with methods and properties to or from rust.
|
|
|
|
|
*
|
|
|
|
|
*
|
|
|
|
|
* The second issue is much harder to get around than the first.
|
|
|
|
|
* In practice it means that if you want to pass an object with callbacks and props to rust, it need to be in seperate args.
|
|
|
|
|
* If you want to pass an object with callbacks and props from rust to js (like ssh handles), you need to instead only pass an object with callbacks
|
|
|
|
|
* just make one of the callbacks a sync info() callback.
|
|
|
|
|
*
|
|
|
|
|
*
|
|
|
|
|
* Then in this api wrapper we can smooth over those rough edges.
|
|
|
|
|
* See: - https://jhugman.github.io/uniffi-bindgen-react-native/idioms/callback-interfaces.html
|
|
|
|
|
*/
|
|
|
|
|
import * as GeneratedRussh from './index';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// #region Ideal API
|
|
|
|
|
|
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
// Core types
|
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
export type PtyType =
|
|
|
|
|
| 'Vanilla' | 'Vt100' | 'Vt102' | 'Vt220' | 'Ansi' | 'Xterm' | 'Xterm256';
|
|
|
|
|
export type TerminalType =
|
|
|
|
|
| 'Vanilla'
|
|
|
|
|
| 'Vt100'
|
|
|
|
|
| 'Vt102'
|
|
|
|
|
| 'Vt220'
|
|
|
|
|
| 'Ansi'
|
|
|
|
|
| 'Xterm'
|
|
|
|
|
| 'Xterm256';
|
|
|
|
|
|
|
|
|
|
export type ConnectionDetails = {
|
|
|
|
|
host: string;
|
|
|
|
|
port: number;
|
|
|
|
|
username: string;
|
|
|
|
|
security:
|
|
|
|
|
| { type: 'password'; password: string }
|
|
|
|
|
| { type: 'key'; privateKey: string };
|
|
|
|
|
| { type: 'password'; password: string }
|
|
|
|
|
| { type: 'key'; privateKey: string };
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* This status is only to provide updates for discrete events
|
|
|
|
|
* during the connect() promise.
|
|
|
|
|
*
|
|
|
|
|
*
|
|
|
|
|
* It is no longer relevant after the connect() promise is resolved.
|
|
|
|
|
*/
|
|
|
|
|
export type SshConnectionProgress =
|
|
|
|
|
| 'tcpConnected' // TCP established, starting SSH handshake
|
|
|
|
|
| 'sshHandshake' // SSH protocol negotiation complete
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 'tcpConnected' // TCP established, starting SSH handshake
|
|
|
|
|
| 'sshHandshake'; // SSH protocol negotiation complete
|
|
|
|
|
|
|
|
|
|
export type ConnectOptions = ConnectionDetails & {
|
|
|
|
|
onConnectionProgress?: (status: SshConnectionProgress) => void;
|
|
|
|
|
@@ -51,31 +54,33 @@ export type ConnectOptions = ConnectionDetails & {
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export type StartShellOptions = {
|
|
|
|
|
pty: PtyType;
|
|
|
|
|
onClosed?: (shellId: string) => void;
|
|
|
|
|
term: TerminalType;
|
|
|
|
|
terminalMode?: GeneratedRussh.TerminalMode[];
|
|
|
|
|
terminalPixelSize?: GeneratedRussh.TerminalPixelSize;
|
|
|
|
|
terminalSize?: GeneratedRussh.TerminalSize;
|
|
|
|
|
onClosed?: (shellId: number) => void;
|
|
|
|
|
abortSignal?: AbortSignal;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export type StreamKind = 'stdout' | 'stderr';
|
|
|
|
|
|
|
|
|
|
export type TerminalChunk = {
|
|
|
|
|
/** Monotonic sequence number from the shell start (Rust u64; JS uses number). */
|
|
|
|
|
seq: number;
|
|
|
|
|
seq: bigint;
|
|
|
|
|
/** Milliseconds since UNIX epoch (double). */
|
|
|
|
|
tMs: number;
|
|
|
|
|
stream: StreamKind;
|
|
|
|
|
bytes: Uint8Array;
|
|
|
|
|
bytes: ArrayBuffer;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export type DropNotice = { kind: 'dropped'; fromSeq: number; toSeq: number };
|
|
|
|
|
export type DropNotice = { kind: 'dropped'; fromSeq: bigint; toSeq: bigint };
|
|
|
|
|
export type ListenerEvent = TerminalChunk | DropNotice;
|
|
|
|
|
|
|
|
|
|
export type Cursor =
|
|
|
|
|
| { mode: 'head' } // earliest available in ring
|
|
|
|
|
| { mode: 'tailBytes'; bytes: number } // last N bytes (best-effort)
|
|
|
|
|
| { mode: 'seq'; seq: number } // from a given sequence
|
|
|
|
|
| { mode: 'time'; tMs: number } // from timestamp
|
|
|
|
|
| { mode: 'live' }; // no replay, live only
|
|
|
|
|
| { mode: 'head' } // earliest available in ring
|
|
|
|
|
| { mode: 'tailBytes'; bytes: bigint } // last N bytes (best-effort)
|
|
|
|
|
| { mode: 'seq'; seq: bigint } // from a given sequence
|
|
|
|
|
| { mode: 'time'; tMs: number } // from timestamp
|
|
|
|
|
| { mode: 'live' }; // no replay, live only
|
|
|
|
|
|
|
|
|
|
export type ListenerOptions = {
|
|
|
|
|
cursor: Cursor;
|
|
|
|
|
@@ -83,30 +88,27 @@ export type ListenerOptions = {
|
|
|
|
|
coalesceMs?: number;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export type BufferStats = {
|
|
|
|
|
ringBytes: number; // configured capacity
|
|
|
|
|
usedBytes: number; // current usage
|
|
|
|
|
chunks: number; // chunks kept
|
|
|
|
|
headSeq: number; // oldest seq retained
|
|
|
|
|
tailSeq: number; // newest seq retained
|
|
|
|
|
droppedBytesTotal: number; // cumulative eviction
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export type BufferReadResult = {
|
|
|
|
|
chunks: TerminalChunk[];
|
|
|
|
|
nextSeq: number;
|
|
|
|
|
dropped?: { fromSeq: number; toSeq: number };
|
|
|
|
|
nextSeq: bigint;
|
|
|
|
|
dropped?: { fromSeq: bigint; toSeq: bigint };
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
// Handles
|
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
type ProgressTimings = {
|
|
|
|
|
tcpEstablishedAtMs: number;
|
|
|
|
|
sshHandshakeAtMs: number;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export type SshConnection = {
|
|
|
|
|
readonly connectionId: string;
|
|
|
|
|
readonly createdAtMs: number;
|
|
|
|
|
readonly tcpEstablishedAtMs: number;
|
|
|
|
|
readonly connectedAtMs: number;
|
|
|
|
|
readonly connectionDetails: ConnectionDetails;
|
|
|
|
|
readonly progressTimings: ProgressTimings;
|
|
|
|
|
|
|
|
|
|
startShell: (opts: StartShellOptions) => Promise<SshShell>;
|
|
|
|
|
disconnect: (opts?: { signal?: AbortSignal }) => Promise<void>;
|
|
|
|
|
@@ -115,20 +117,26 @@ export type SshConnection = {
|
|
|
|
|
export type SshShell = {
|
|
|
|
|
readonly channelId: number;
|
|
|
|
|
readonly createdAtMs: number;
|
|
|
|
|
readonly pty: PtyType;
|
|
|
|
|
readonly pty: TerminalType;
|
|
|
|
|
readonly connectionId: string;
|
|
|
|
|
|
|
|
|
|
// I/O
|
|
|
|
|
sendData: (data: ArrayBuffer, opts?: { signal?: AbortSignal }) => Promise<void>;
|
|
|
|
|
sendData: (
|
|
|
|
|
data: ArrayBuffer,
|
|
|
|
|
opts?: { signal?: AbortSignal }
|
|
|
|
|
) => Promise<void>;
|
|
|
|
|
close: (opts?: { signal?: AbortSignal }) => Promise<void>;
|
|
|
|
|
|
|
|
|
|
// Buffer policy & stats
|
|
|
|
|
setBufferPolicy: (policy: { ringBytes?: number; coalesceMs?: number }) => Promise<void>;
|
|
|
|
|
bufferStats: () => Promise<BufferStats>;
|
|
|
|
|
currentSeq: () => Promise<number>;
|
|
|
|
|
// setBufferPolicy: (policy: {
|
|
|
|
|
// ringBytes?: number;
|
|
|
|
|
// coalesceMs?: number;
|
|
|
|
|
// }) => Promise<void>;
|
|
|
|
|
bufferStats: () => GeneratedRussh.BufferStats;
|
|
|
|
|
currentSeq: () => number;
|
|
|
|
|
|
|
|
|
|
// Replay + live
|
|
|
|
|
readBuffer: (cursor: Cursor, maxBytes?: number) => Promise<BufferReadResult>;
|
|
|
|
|
readBuffer: (cursor: Cursor, maxBytes?: bigint) => BufferReadResult;
|
|
|
|
|
addListener: (
|
|
|
|
|
cb: (ev: ListenerEvent) => void,
|
|
|
|
|
opts: ListenerOptions
|
|
|
|
|
@@ -146,41 +154,55 @@ type RusshApi = {
|
|
|
|
|
|
|
|
|
|
// #region Wrapper to match the ideal API
|
|
|
|
|
|
|
|
|
|
const ptyTypeLiteralToEnum = {
|
|
|
|
|
Vanilla: GeneratedRussh.PtyType.Vanilla,
|
|
|
|
|
Vt100: GeneratedRussh.PtyType.Vt100,
|
|
|
|
|
Vt102: GeneratedRussh.PtyType.Vt102,
|
|
|
|
|
Vt220: GeneratedRussh.PtyType.Vt220,
|
|
|
|
|
Ansi: GeneratedRussh.PtyType.Ansi,
|
|
|
|
|
Xterm: GeneratedRussh.PtyType.Xterm,
|
|
|
|
|
Xterm256: GeneratedRussh.PtyType.Xterm256,
|
|
|
|
|
} as const satisfies Record<string, GeneratedRussh.PtyType>;
|
|
|
|
|
const terminalTypeLiteralToEnum = {
|
|
|
|
|
Vanilla: GeneratedRussh.TerminalType.Vanilla,
|
|
|
|
|
Vt100: GeneratedRussh.TerminalType.Vt100,
|
|
|
|
|
Vt102: GeneratedRussh.TerminalType.Vt102,
|
|
|
|
|
Vt220: GeneratedRussh.TerminalType.Vt220,
|
|
|
|
|
Ansi: GeneratedRussh.TerminalType.Ansi,
|
|
|
|
|
Xterm: GeneratedRussh.TerminalType.Xterm,
|
|
|
|
|
Xterm256: GeneratedRussh.TerminalType.Xterm256,
|
|
|
|
|
} as const satisfies Record<string, GeneratedRussh.TerminalType>;
|
|
|
|
|
|
|
|
|
|
const ptyEnumToLiteral: Record<GeneratedRussh.PtyType, PtyType> = {
|
|
|
|
|
[GeneratedRussh.PtyType.Vanilla]: 'Vanilla',
|
|
|
|
|
[GeneratedRussh.PtyType.Vt100]: 'Vt100',
|
|
|
|
|
[GeneratedRussh.PtyType.Vt102]: 'Vt102',
|
|
|
|
|
[GeneratedRussh.PtyType.Vt220]: 'Vt220',
|
|
|
|
|
[GeneratedRussh.PtyType.Ansi]: 'Ansi',
|
|
|
|
|
[GeneratedRussh.PtyType.Xterm]: 'Xterm',
|
|
|
|
|
[GeneratedRussh.PtyType.Xterm256]: 'Xterm256',
|
|
|
|
|
const terminalTypeEnumToLiteral: Record<
|
|
|
|
|
GeneratedRussh.TerminalType,
|
|
|
|
|
TerminalType
|
|
|
|
|
> = {
|
|
|
|
|
[GeneratedRussh.TerminalType.Vanilla]: 'Vanilla',
|
|
|
|
|
[GeneratedRussh.TerminalType.Vt100]: 'Vt100',
|
|
|
|
|
[GeneratedRussh.TerminalType.Vt102]: 'Vt102',
|
|
|
|
|
[GeneratedRussh.TerminalType.Vt220]: 'Vt220',
|
|
|
|
|
[GeneratedRussh.TerminalType.Ansi]: 'Ansi',
|
|
|
|
|
[GeneratedRussh.TerminalType.Xterm]: 'Xterm',
|
|
|
|
|
[GeneratedRussh.TerminalType.Xterm256]: 'Xterm256',
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const sshConnStatusEnumToLiteral = {
|
|
|
|
|
[GeneratedRussh.SshConnectionStatus.TcpConnected]: 'tcpConnected',
|
|
|
|
|
[GeneratedRussh.SshConnectionStatus.SshHandshake]: 'sshHandshake',
|
|
|
|
|
} as const satisfies Record<GeneratedRussh.SshConnectionStatus, SshConnectionProgress>;
|
|
|
|
|
const sshConnProgressEnumToLiteral = {
|
|
|
|
|
[GeneratedRussh.SshConnectionProgressEvent.TcpConnected]: 'tcpConnected',
|
|
|
|
|
[GeneratedRussh.SshConnectionProgressEvent.SshHandshake]: 'sshHandshake',
|
|
|
|
|
} as const satisfies Record<
|
|
|
|
|
GeneratedRussh.SshConnectionProgressEvent,
|
|
|
|
|
SshConnectionProgress
|
|
|
|
|
>;
|
|
|
|
|
|
|
|
|
|
const streamEnumToLiteral = {
|
|
|
|
|
[GeneratedRussh.StreamKind.Stdout]: 'stdout',
|
|
|
|
|
[GeneratedRussh.StreamKind.Stderr]: 'stderr',
|
|
|
|
|
} as const satisfies Record<GeneratedRussh.StreamKind, StreamKind>;
|
|
|
|
|
|
|
|
|
|
function generatedConnDetailsToIdeal(details: GeneratedRussh.ConnectionDetails): ConnectionDetails {
|
|
|
|
|
const security: ConnectionDetails['security'] = details.security instanceof GeneratedRussh.Security.Password
|
|
|
|
|
? { type: 'password', password: details.security.inner.password }
|
|
|
|
|
: { type: 'key', privateKey: details.security.inner.keyId };
|
|
|
|
|
return { host: details.host, port: details.port, username: details.username, security };
|
|
|
|
|
function generatedConnDetailsToIdeal(
|
|
|
|
|
details: GeneratedRussh.ConnectionDetails
|
|
|
|
|
): ConnectionDetails {
|
|
|
|
|
const security: ConnectionDetails['security'] =
|
|
|
|
|
details.security instanceof GeneratedRussh.Security.Password
|
|
|
|
|
? { type: 'password', password: details.security.inner.password }
|
|
|
|
|
: { type: 'key', privateKey: details.security.inner.privateKeyContent };
|
|
|
|
|
return {
|
|
|
|
|
host: details.host,
|
|
|
|
|
port: details.port,
|
|
|
|
|
username: details.username,
|
|
|
|
|
security,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function cursorToGenerated(cursor: Cursor): GeneratedRussh.Cursor {
|
|
|
|
|
@@ -188,9 +210,11 @@ function cursorToGenerated(cursor: Cursor): GeneratedRussh.Cursor {
|
|
|
|
|
case 'head':
|
|
|
|
|
return new GeneratedRussh.Cursor.Head();
|
|
|
|
|
case 'tailBytes':
|
|
|
|
|
return new GeneratedRussh.Cursor.TailBytes({ bytes: BigInt(cursor.bytes) });
|
|
|
|
|
return new GeneratedRussh.Cursor.TailBytes({
|
|
|
|
|
bytes: cursor.bytes,
|
|
|
|
|
});
|
|
|
|
|
case 'seq':
|
|
|
|
|
return new GeneratedRussh.Cursor.Seq({ seq: BigInt(cursor.seq) });
|
|
|
|
|
return new GeneratedRussh.Cursor.Seq({ seq: cursor.seq });
|
|
|
|
|
case 'time':
|
|
|
|
|
return new GeneratedRussh.Cursor.TimeMs({ tMs: cursor.tMs });
|
|
|
|
|
case 'live':
|
|
|
|
|
@@ -200,38 +224,24 @@ function cursorToGenerated(cursor: Cursor): GeneratedRussh.Cursor {
|
|
|
|
|
|
|
|
|
|
function toTerminalChunk(ch: GeneratedRussh.TerminalChunk): TerminalChunk {
|
|
|
|
|
return {
|
|
|
|
|
seq: Number(ch.seq),
|
|
|
|
|
seq: ch.seq,
|
|
|
|
|
tMs: ch.tMs,
|
|
|
|
|
stream: streamEnumToLiteral[ch.stream],
|
|
|
|
|
bytes: new Uint8Array(ch.bytes as any),
|
|
|
|
|
bytes: ch.bytes,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function wrapShellSession(shell: GeneratedRussh.ShellSessionInterface): SshShell {
|
|
|
|
|
const info = shell.info();
|
|
|
|
|
function wrapShellSession(
|
|
|
|
|
shell: GeneratedRussh.ShellSessionInterface
|
|
|
|
|
): SshShell {
|
|
|
|
|
const info = shell.getInfo();
|
|
|
|
|
|
|
|
|
|
const setBufferPolicy: SshShell['setBufferPolicy'] = async (policy) => {
|
|
|
|
|
await shell.setBufferPolicy(policy.ringBytes != null ? BigInt(policy.ringBytes) : undefined, policy.coalesceMs);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const bufferStats: SshShell['bufferStats'] = async () => {
|
|
|
|
|
const s = shell.bufferStats();
|
|
|
|
|
return {
|
|
|
|
|
ringBytes: Number(s.ringBytes),
|
|
|
|
|
usedBytes: Number(s.usedBytes),
|
|
|
|
|
chunks: Number(s.chunks),
|
|
|
|
|
headSeq: Number(s.headSeq),
|
|
|
|
|
tailSeq: Number(s.tailSeq),
|
|
|
|
|
droppedBytesTotal: Number(s.droppedBytesTotal),
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const readBuffer: SshShell['readBuffer'] = async (cursor, maxBytes) => {
|
|
|
|
|
const res = shell.readBuffer(cursorToGenerated(cursor), maxBytes != null ? BigInt(maxBytes) : undefined);
|
|
|
|
|
const readBuffer: SshShell['readBuffer'] = (cursor, maxBytes) => {
|
|
|
|
|
const res = shell.readBuffer(cursorToGenerated(cursor), maxBytes);
|
|
|
|
|
return {
|
|
|
|
|
chunks: res.chunks.map(toTerminalChunk),
|
|
|
|
|
nextSeq: Number(res.nextSeq),
|
|
|
|
|
dropped: res.dropped ? { fromSeq: Number(res.dropped.fromSeq), toSeq: Number(res.dropped.toSeq) } : undefined,
|
|
|
|
|
nextSeq: res.nextSeq,
|
|
|
|
|
dropped: res.dropped,
|
|
|
|
|
} satisfies BufferReadResult;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
@@ -241,87 +251,128 @@ function wrapShellSession(shell: GeneratedRussh.ShellSessionInterface): SshShell
|
|
|
|
|
if (ev instanceof GeneratedRussh.ShellEvent.Chunk) {
|
|
|
|
|
cb(toTerminalChunk(ev.inner[0]!));
|
|
|
|
|
} else if (ev instanceof GeneratedRussh.ShellEvent.Dropped) {
|
|
|
|
|
cb({ kind: 'dropped', fromSeq: Number(ev.inner.fromSeq), toSeq: Number(ev.inner.toSeq) });
|
|
|
|
|
cb({
|
|
|
|
|
kind: 'dropped',
|
|
|
|
|
fromSeq: ev.inner.fromSeq,
|
|
|
|
|
toSeq: ev.inner.toSeq,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
} satisfies GeneratedRussh.ShellListener;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const id = shell.addListener(listener, { cursor: cursorToGenerated(opts.cursor), coalesceMs: opts.coalesceMs });
|
|
|
|
|
const id = shell.addListener(listener, {
|
|
|
|
|
cursor: cursorToGenerated(opts.cursor),
|
|
|
|
|
coalesceMs: opts.coalesceMs,
|
|
|
|
|
});
|
|
|
|
|
if (id === 0n) {
|
|
|
|
|
throw new Error('Failed to attach shell listener (id=0)');
|
|
|
|
|
}
|
|
|
|
|
return BigInt(id);
|
|
|
|
|
return id;
|
|
|
|
|
} catch (e) {
|
|
|
|
|
throw new Error(`addListener failed: ${String((e as any)?.message ?? e)}`);
|
|
|
|
|
throw new Error(
|
|
|
|
|
`addListener failed: ${String((e as any)?.message ?? e)}`
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
channelId: info.channelId,
|
|
|
|
|
createdAtMs: info.createdAtMs,
|
|
|
|
|
pty: ptyEnumToLiteral[info.pty],
|
|
|
|
|
pty: terminalTypeEnumToLiteral[info.term],
|
|
|
|
|
connectionId: info.connectionId,
|
|
|
|
|
sendData: (data, o) => shell.sendData(data, o?.signal ? { signal: o.signal } : undefined),
|
|
|
|
|
sendData: (data, o) =>
|
|
|
|
|
shell.sendData(data, o?.signal ? { signal: o.signal } : undefined),
|
|
|
|
|
close: (o) => shell.close(o?.signal ? { signal: o.signal } : undefined),
|
|
|
|
|
setBufferPolicy,
|
|
|
|
|
bufferStats,
|
|
|
|
|
currentSeq: async () => Number(shell.currentSeq()),
|
|
|
|
|
// setBufferPolicy,
|
|
|
|
|
bufferStats: shell.bufferStats,
|
|
|
|
|
currentSeq: () => Number(shell.currentSeq()),
|
|
|
|
|
readBuffer,
|
|
|
|
|
addListener,
|
|
|
|
|
removeListener: (id) => shell.removeListener(id),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function wrapConnection(conn: GeneratedRussh.SshConnectionInterface): SshConnection {
|
|
|
|
|
const inf = conn.info();
|
|
|
|
|
function wrapConnection(
|
|
|
|
|
conn: GeneratedRussh.SshConnectionInterface
|
|
|
|
|
): SshConnection {
|
|
|
|
|
const info = conn.getInfo();
|
|
|
|
|
return {
|
|
|
|
|
connectionId: inf.connectionId,
|
|
|
|
|
connectionDetails: generatedConnDetailsToIdeal(inf.connectionDetails),
|
|
|
|
|
createdAtMs: inf.createdAtMs,
|
|
|
|
|
tcpEstablishedAtMs: inf.tcpEstablishedAtMs,
|
|
|
|
|
connectionId: info.connectionId,
|
|
|
|
|
connectionDetails: generatedConnDetailsToIdeal(info.connectionDetails),
|
|
|
|
|
createdAtMs: info.createdAtMs,
|
|
|
|
|
connectedAtMs: info.connectedAtMs,
|
|
|
|
|
progressTimings: {
|
|
|
|
|
tcpEstablishedAtMs: info.progressTimings.tcpEstablishedAtMs,
|
|
|
|
|
sshHandshakeAtMs: info.progressTimings.sshHandshakeAtMs,
|
|
|
|
|
},
|
|
|
|
|
startShell: async (params) => {
|
|
|
|
|
const shell = await conn.startShell(
|
|
|
|
|
{
|
|
|
|
|
pty: ptyTypeLiteralToEnum[params.pty],
|
|
|
|
|
onStatusChange: params.onStatusChange
|
|
|
|
|
? { onChange: (statusEnum) => params.onStatusChange!(sshConnStatusEnumToLiteral[statusEnum]) }
|
|
|
|
|
term: terminalTypeLiteralToEnum[params.term],
|
|
|
|
|
onClosedCallback: params.onClosed
|
|
|
|
|
? {
|
|
|
|
|
onChange: (channelId) => params.onClosed!(channelId),
|
|
|
|
|
}
|
|
|
|
|
: undefined,
|
|
|
|
|
terminalMode: params.terminalMode,
|
|
|
|
|
terminalPixelSize: params.terminalPixelSize,
|
|
|
|
|
terminalSize: params.terminalSize,
|
|
|
|
|
},
|
|
|
|
|
params.abortSignal ? { signal: params.abortSignal } : undefined,
|
|
|
|
|
params.abortSignal ? { signal: params.abortSignal } : undefined
|
|
|
|
|
);
|
|
|
|
|
return wrapShellSession(shell);
|
|
|
|
|
},
|
|
|
|
|
disconnect: (opts) => conn.disconnect(opts?.signal ? { signal: opts.signal } : undefined),
|
|
|
|
|
disconnect: (opts) =>
|
|
|
|
|
conn.disconnect(opts?.signal ? { signal: opts.signal } : undefined),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function connect(options: ConnectOptions): Promise<SshConnection> {
|
|
|
|
|
const security =
|
|
|
|
|
options.security.type === 'password'
|
|
|
|
|
? new GeneratedRussh.Security.Password({ password: options.security.password })
|
|
|
|
|
: new GeneratedRussh.Security.Key({ keyId: options.security.privateKey });
|
|
|
|
|
? new GeneratedRussh.Security.Password({
|
|
|
|
|
password: options.security.password,
|
|
|
|
|
})
|
|
|
|
|
: new GeneratedRussh.Security.Key({
|
|
|
|
|
privateKeyContent: options.security.privateKey,
|
|
|
|
|
});
|
|
|
|
|
const sshConnection = await GeneratedRussh.connect(
|
|
|
|
|
{
|
|
|
|
|
host: options.host,
|
|
|
|
|
port: options.port,
|
|
|
|
|
username: options.username,
|
|
|
|
|
security,
|
|
|
|
|
onStatusChange: options.onStatusChange ? {
|
|
|
|
|
onChange: (statusEnum) => options.onStatusChange!(sshConnStatusEnumToLiteral[statusEnum])
|
|
|
|
|
} : undefined,
|
|
|
|
|
connectionDetails: {
|
|
|
|
|
host: options.host,
|
|
|
|
|
port: options.port,
|
|
|
|
|
username: options.username,
|
|
|
|
|
security,
|
|
|
|
|
},
|
|
|
|
|
onConnectionProgressCallback: options.onConnectionProgress
|
|
|
|
|
? {
|
|
|
|
|
onChange: (statusEnum) =>
|
|
|
|
|
options.onConnectionProgress!(
|
|
|
|
|
sshConnProgressEnumToLiteral[statusEnum]
|
|
|
|
|
),
|
|
|
|
|
}
|
|
|
|
|
: undefined,
|
|
|
|
|
onDisconnectedCallback: options.onDisconnected
|
|
|
|
|
? {
|
|
|
|
|
onChange: (connectionId) => options.onDisconnected!(connectionId),
|
|
|
|
|
}
|
|
|
|
|
: undefined,
|
|
|
|
|
},
|
|
|
|
|
options.abortSignal ? { signal: options.abortSignal } : undefined,
|
|
|
|
|
options.abortSignal ? { signal: options.abortSignal } : undefined
|
|
|
|
|
);
|
|
|
|
|
return wrapConnection(sshConnection);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function generateKeyPair(type: 'rsa' | 'ecdsa' | 'ed25519') {
|
|
|
|
|
const map = { rsa: GeneratedRussh.KeyType.Rsa, ecdsa: GeneratedRussh.KeyType.Ecdsa, ed25519: GeneratedRussh.KeyType.Ed25519 } as const;
|
|
|
|
|
const map = {
|
|
|
|
|
rsa: GeneratedRussh.KeyType.Rsa,
|
|
|
|
|
ecdsa: GeneratedRussh.KeyType.Ecdsa,
|
|
|
|
|
ed25519: GeneratedRussh.KeyType.Ed25519,
|
|
|
|
|
} as const;
|
|
|
|
|
return GeneratedRussh.generateKeyPair(map[type]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// #endregion
|
|
|
|
|
|
|
|
|
|
export const RnRussh = {
|
|
|
|
|
|