chore: Adds initial support for eslint.
This commit is contained in:
parent
8b4f3715e9
commit
7e80b298a2
34 changed files with 3441 additions and 649 deletions
|
@ -1,5 +1,16 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
shopt -s globstar
|
shopt -s globstar
|
||||||
|
set -e
|
||||||
|
|
||||||
|
pushd $(git rev-parse --show-toplevel)/api
|
||||||
|
npx eslint src
|
||||||
|
popd
|
||||||
|
|
||||||
|
pushd $(git rev-parse --show-toplevel)/lom
|
||||||
|
npx eslint src
|
||||||
|
popd
|
||||||
|
|
||||||
|
# Formatting step
|
||||||
"$(git rev-parse --show-toplevel)"/api/node_modules/.bin/prettier --ignore-unknown --write $(git rev-parse --show-toplevel)/{api,lom}/src/**/*.ts
|
"$(git rev-parse --show-toplevel)"/api/node_modules/.bin/prettier --ignore-unknown --write $(git rev-parse --show-toplevel)/{api,lom}/src/**/*.ts
|
||||||
rustfmt $(git rev-parse --show-toplevel)/gui/src/**/*.rs
|
rustfmt $(git rev-parse --show-toplevel)/gui/src/**/*.rs
|
||||||
git update-index --again
|
git update-index --again
|
||||||
|
|
10
api/eslint.config.js
Normal file
10
api/eslint.config.js
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import globals from "globals";
|
||||||
|
import pluginJs from "@eslint/js";
|
||||||
|
import tseslint from "typescript-eslint";
|
||||||
|
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{languageOptions: { globals: globals.node }},
|
||||||
|
pluginJs.configs.recommended,
|
||||||
|
...tseslint.configs.recommended,
|
||||||
|
];
|
1327
api/package-lock.json
generated
1327
api/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -14,14 +14,18 @@
|
||||||
"author": "greysoh",
|
"author": "greysoh",
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^9.2.0",
|
||||||
"@types/bcrypt": "^5.0.2",
|
"@types/bcrypt": "^5.0.2",
|
||||||
"@types/node": "^20.12.7",
|
"@types/node": "^20.12.7",
|
||||||
"@types/ssh2": "^1.15.0",
|
"@types/ssh2": "^1.15.0",
|
||||||
"@types/ws": "^8.5.10",
|
"@types/ws": "^8.5.10",
|
||||||
|
"eslint": "^8.57.0",
|
||||||
|
"globals": "^15.2.0",
|
||||||
"nodemon": "^3.0.3",
|
"nodemon": "^3.0.3",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.2.5",
|
||||||
"prisma": "^5.13.0",
|
"prisma": "^5.13.0",
|
||||||
"typescript": "^5.3.3"
|
"typescript": "^5.3.3",
|
||||||
|
"typescript-eslint": "^7.8.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fastify/websocket": "^10.0.1",
|
"@fastify/websocket": "^10.0.1",
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
// @eslint-ignore-file
|
||||||
|
|
||||||
export type ParameterReturnedValue = {
|
export type ParameterReturnedValue = {
|
||||||
success: boolean;
|
success: boolean;
|
||||||
message?: string;
|
message?: string;
|
||||||
|
|
|
@ -10,4 +10,4 @@ export const backendProviders: Record<string, typeof BackendBaseClass> = {
|
||||||
|
|
||||||
if (process.env.NODE_ENV != "production") {
|
if (process.env.NODE_ENV != "production") {
|
||||||
backendProviders["dummy"] = BackendBaseClass;
|
backendProviders["dummy"] = BackendBaseClass;
|
||||||
}
|
}
|
||||||
|
|
|
@ -216,7 +216,7 @@ export class PassyFireBackendProvider implements BackendBaseClass {
|
||||||
static checkParametersBackendInstance(data: string): ParameterReturnedValue {
|
static checkParametersBackendInstance(data: string): ParameterReturnedValue {
|
||||||
try {
|
try {
|
||||||
parseBackendProviderString(data);
|
parseBackendProviderString(data);
|
||||||
// @ts-ignore
|
// @ts-expect-error
|
||||||
} catch (e: Error) {
|
} catch (e: Error) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
|
|
|
@ -69,7 +69,7 @@ export function route(instance: PassyFireBackendProvider) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
(req, res) => {
|
(req, res) => {
|
||||||
// @ts-ignore
|
// @ts-expect-error
|
||||||
const body: {
|
const body: {
|
||||||
username: string;
|
username: string;
|
||||||
password: string;
|
password: string;
|
||||||
|
@ -115,7 +115,7 @@ export function route(instance: PassyFireBackendProvider) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
// @ts-ignore
|
// @ts-expect-error
|
||||||
const body: {
|
const body: {
|
||||||
token: string;
|
token: string;
|
||||||
} = req.body;
|
} = req.body;
|
||||||
|
@ -132,7 +132,7 @@ export function route(instance: PassyFireBackendProvider) {
|
||||||
req.hostname.indexOf(":") + 1,
|
req.hostname.indexOf(":") + 1,
|
||||||
);
|
);
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-expect-error
|
||||||
// parseInt(...) can take a number just fine, at least in Node.JS
|
// parseInt(...) can take a number just fine, at least in Node.JS
|
||||||
const port = parseInt(unparsedPort == "" ? proxiedPort : unparsedPort);
|
const port = parseInt(unparsedPort == "" ? proxiedPort : unparsedPort);
|
||||||
|
|
||||||
|
|
|
@ -57,7 +57,7 @@ export function requestHandler(
|
||||||
let state: "authentication" | "data" = "authentication";
|
let state: "authentication" | "data" = "authentication";
|
||||||
let socket: dgram.Socket | net.Socket | undefined;
|
let socket: dgram.Socket | net.Socket | undefined;
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-expect-error
|
||||||
let connectedClient: ConnectedClientExt = {};
|
let connectedClient: ConnectedClientExt = {};
|
||||||
|
|
||||||
ws.on("close", () => {
|
ws.on("close", () => {
|
||||||
|
|
|
@ -92,7 +92,7 @@ export class SSHBackendProvider implements BackendBaseClass {
|
||||||
this.logs.push(`Failed to start SSHBackendProvider! Error: '${e}'`);
|
this.logs.push(`Failed to start SSHBackendProvider! Error: '${e}'`);
|
||||||
this.state = "stopped";
|
this.state = "stopped";
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-expect-error
|
||||||
this.sshInstance = null;
|
this.sshInstance = null;
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
@ -112,7 +112,7 @@ export class SSHBackendProvider implements BackendBaseClass {
|
||||||
|
|
||||||
this.sshInstance.dispose();
|
this.sshInstance.dispose();
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-expect-error
|
||||||
this.sshInstance = null;
|
this.sshInstance = null;
|
||||||
|
|
||||||
this.logs.push("Successfully stopped SSHBackendProvider.");
|
this.logs.push("Successfully stopped SSHBackendProvider.");
|
||||||
|
@ -255,7 +255,7 @@ export class SSHBackendProvider implements BackendBaseClass {
|
||||||
static checkParametersBackendInstance(data: string): ParameterReturnedValue {
|
static checkParametersBackendInstance(data: string): ParameterReturnedValue {
|
||||||
try {
|
try {
|
||||||
parseBackendProviderString(data);
|
parseBackendProviderString(data);
|
||||||
// @ts-ignore
|
// @ts-expect-error
|
||||||
} catch (e: Error) {
|
} catch (e: Error) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
|
|
|
@ -27,7 +27,7 @@ export const permissionListDisabled: Record<string, boolean> = {
|
||||||
};
|
};
|
||||||
|
|
||||||
// FIXME: This solution fucking sucks.
|
// FIXME: This solution fucking sucks.
|
||||||
export let permissionListEnabled: Record<string, boolean> = JSON.parse(
|
export const permissionListEnabled: Record<string, boolean> = JSON.parse(
|
||||||
JSON.stringify(permissionListDisabled),
|
JSON.stringify(permissionListDisabled),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,7 @@ export function route(routeOptions: RouteOptions) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
// @ts-ignore
|
// @ts-expect-error
|
||||||
const body: {
|
const body: {
|
||||||
token: string;
|
token: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
|
|
@ -33,7 +33,7 @@ export function route(routeOptions: RouteOptions) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
// @ts-ignore
|
// @ts-expect-error
|
||||||
const body: {
|
const body: {
|
||||||
token: string;
|
token: string;
|
||||||
id?: number;
|
id?: number;
|
||||||
|
|
|
@ -30,7 +30,7 @@ export function route(routeOptions: RouteOptions) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
// @ts-ignore
|
// @ts-expect-error
|
||||||
const body: {
|
const body: {
|
||||||
token: string;
|
token: string;
|
||||||
id: number;
|
id: number;
|
||||||
|
|
|
@ -27,7 +27,7 @@ export function route(routeOptions: RouteOptions) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
// @ts-ignore
|
// @ts-expect-error
|
||||||
const body: {
|
const body: {
|
||||||
token: string;
|
token: string;
|
||||||
id: number;
|
id: number;
|
||||||
|
|
|
@ -50,7 +50,7 @@ export function route(routeOptions: RouteOptions) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
// @ts-ignore
|
// @ts-expect-error
|
||||||
const body: {
|
const body: {
|
||||||
token: string;
|
token: string;
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,7 @@ export function route(routeOptions: RouteOptions) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
// @ts-ignore
|
// @ts-expect-error
|
||||||
const body: {
|
const body: {
|
||||||
token: string;
|
token: string;
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ export function route(routeOptions: RouteOptions) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
// @ts-ignore
|
// @ts-expect-error
|
||||||
const body: {
|
const body: {
|
||||||
token: string;
|
token: string;
|
||||||
id: number;
|
id: number;
|
||||||
|
|
|
@ -30,7 +30,7 @@ export function route(routeOptions: RouteOptions) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
// @ts-ignore
|
// @ts-expect-error
|
||||||
const body: {
|
const body: {
|
||||||
token: string;
|
token: string;
|
||||||
id: number;
|
id: number;
|
||||||
|
@ -59,7 +59,7 @@ export function route(routeOptions: RouteOptions) {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Other restrictions in place make it so that it MUST be either TCP or UDP
|
// Other restrictions in place make it so that it MUST be either TCP or UDP
|
||||||
// @ts-ignore
|
// @ts-expect-error
|
||||||
const protocol: "tcp" | "udp" = forward.protocol;
|
const protocol: "tcp" | "udp" = forward.protocol;
|
||||||
|
|
||||||
backends[forward.destProviderID].addConnection(
|
backends[forward.destProviderID].addConnection(
|
||||||
|
|
|
@ -30,7 +30,7 @@ export function route(routeOptions: RouteOptions) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
// @ts-ignore
|
// @ts-expect-error
|
||||||
const body: {
|
const body: {
|
||||||
token: string;
|
token: string;
|
||||||
id: number;
|
id: number;
|
||||||
|
@ -59,7 +59,7 @@ export function route(routeOptions: RouteOptions) {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Other restrictions in place make it so that it MUST be either TCP or UDP
|
// Other restrictions in place make it so that it MUST be either TCP or UDP
|
||||||
// @ts-ignore
|
// @ts-expect-error
|
||||||
const protocol: "tcp" | "udp" = forward.protocol;
|
const protocol: "tcp" | "udp" = forward.protocol;
|
||||||
|
|
||||||
backends[forward.destProviderID].removeConnection(
|
backends[forward.destProviderID].removeConnection(
|
||||||
|
|
|
@ -22,7 +22,7 @@ export function route(routeOptions: RouteOptions) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
// @ts-ignore
|
// @ts-expect-error
|
||||||
const body: {
|
const body: {
|
||||||
token: string;
|
token: string;
|
||||||
} = req.body;
|
} = req.body;
|
||||||
|
|
|
@ -29,7 +29,7 @@ export function route(routeOptions: RouteOptions) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
// @ts-ignore
|
// @ts-expect-error
|
||||||
const body: {
|
const body: {
|
||||||
name: string;
|
name: string;
|
||||||
email: string;
|
email: string;
|
||||||
|
@ -87,9 +87,9 @@ export function route(routeOptions: RouteOptions) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.allowUnsafeGlobalTokens) {
|
if (options.allowUnsafeGlobalTokens) {
|
||||||
// @ts-ignore
|
// @ts-expect-error
|
||||||
userData.rootToken = generateRandomData();
|
userData.rootToken = generateRandomData();
|
||||||
// @ts-ignore
|
// @ts-expect-error
|
||||||
userData.isRootServiceAccount = true;
|
userData.isRootServiceAccount = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ export function route(routeOptions: RouteOptions) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
// @ts-ignore
|
// @ts-expect-error
|
||||||
const body: {
|
const body: {
|
||||||
email?: string;
|
email?: string;
|
||||||
username?: string;
|
username?: string;
|
||||||
|
|
|
@ -31,7 +31,7 @@ export function route(routeOptions: RouteOptions) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
// @ts-ignore
|
// @ts-expect-error
|
||||||
const body: {
|
const body: {
|
||||||
token: string;
|
token: string;
|
||||||
id?: number;
|
id?: number;
|
||||||
|
|
|
@ -30,7 +30,7 @@ export function route(routeOptions: RouteOptions) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
async (req, res) => {
|
async (req, res) => {
|
||||||
// @ts-ignore
|
// @ts-expect-error
|
||||||
const body: {
|
const body: {
|
||||||
token: string;
|
token: string;
|
||||||
uid: number;
|
uid: number;
|
||||||
|
|
149
lom/diff.diff
Normal file
149
lom/diff.diff
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
diff --git a/api/src/backendimpl/passyfire-reimpl/routes.ts b/api/src/backendimpl/passyfire-reimpl/routes.ts
|
||||||
|
index 2961483..4519a87 100644
|
||||||
|
--- a/api/src/backendimpl/passyfire-reimpl/routes.ts
|
||||||
|
+++ b/api/src/backendimpl/passyfire-reimpl/routes.ts
|
||||||
|
@@ -47,25 +47,6 @@ export function route(instance: PassyFireBackendProvider) {
|
||||||
|
|
||||||
|
for (const spoofedRoute of unsupportedSpoofedRoutes) {
|
||||||
|
fastify.post(spoofedRoute, (req, res) => {
|
||||||
|
- if (typeof req.body != "string")
|
||||||
|
- return res.status(400).send({
|
||||||
|
- error: "Invalid token",
|
||||||
|
- });
|
||||||
|
-
|
||||||
|
- try {
|
||||||
|
- JSON.parse(req.body);
|
||||||
|
- } catch (e) {
|
||||||
|
- return res.status(400).send({
|
||||||
|
- error: "Invalid token",
|
||||||
|
- });
|
||||||
|
- }
|
||||||
|
-
|
||||||
|
- // @ts-expect-error
|
||||||
|
- if (!req.body.token)
|
||||||
|
- return res.status(400).send({
|
||||||
|
- error: "Invalid token",
|
||||||
|
- });
|
||||||
|
-
|
||||||
|
return res.status(403).send({
|
||||||
|
error: "Invalid scope(s)",
|
||||||
|
});
|
||||||
|
diff --git a/lom/src/commands/backends.ts b/lom/src/commands/backends.ts
|
||||||
|
index baba3d1..d16cac1 100644
|
||||||
|
--- a/lom/src/commands/backends.ts
|
||||||
|
+++ b/lom/src/commands/backends.ts
|
||||||
|
@@ -16,6 +16,18 @@ type BackendLookupSuccess = {
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
|
||||||
|
+const addRequiredOptions = {
|
||||||
|
+ ssh: [
|
||||||
|
+ "sshKey",
|
||||||
|
+ "username",
|
||||||
|
+ "host",
|
||||||
|
+ ],
|
||||||
|
+
|
||||||
|
+ passyfire: [
|
||||||
|
+ "host"
|
||||||
|
+ ]
|
||||||
|
+};
|
||||||
|
+
|
||||||
|
export async function run(
|
||||||
|
argv: string[],
|
||||||
|
println: PrintLine,
|
||||||
|
@@ -58,12 +70,12 @@ export async function run(
|
||||||
|
);
|
||||||
|
|
||||||
|
addBackend.option(
|
||||||
|
- "-u, --username",
|
||||||
|
+ "-u, --username <user>",
|
||||||
|
"(SSH, PassyFire) Username to authenticate with. With PassyFire, it's the username you create",
|
||||||
|
);
|
||||||
|
|
||||||
|
addBackend.option(
|
||||||
|
- "-h, --host",
|
||||||
|
+ "-h, --host <host>",
|
||||||
|
"(SSH, PassyFire) Host to connect to. With PassyFire, it's what you listen on",
|
||||||
|
);
|
||||||
|
|
||||||
|
@@ -86,10 +98,70 @@ export async function run(
|
||||||
|
);
|
||||||
|
|
||||||
|
addBackend.option(
|
||||||
|
- "-p, --password",
|
||||||
|
+ "-p, --password <password>",
|
||||||
|
"(PassyFire) What password you want to use for the primary user",
|
||||||
|
);
|
||||||
|
|
||||||
|
+ addBackend.action(async(name: string, provider: string, options: {
|
||||||
|
+ description?: string,
|
||||||
|
+ forceCustomParameters?: boolean,
|
||||||
|
+ customParameters?: string,
|
||||||
|
+
|
||||||
|
+ // SSH (mostly)
|
||||||
|
+ sshKey?: string,
|
||||||
|
+ username?: string,
|
||||||
|
+ host?: string,
|
||||||
|
+
|
||||||
|
+ // PassyFire (mostly)
|
||||||
|
+ isProxied?: boolean,
|
||||||
|
+ proxiedPortStr?: number,
|
||||||
|
+ guest?: boolean,
|
||||||
|
+ userAsk?: boolean,
|
||||||
|
+ password?: string
|
||||||
|
+ }) => {
|
||||||
|
+ // Yes it can index for what we need it to do.
|
||||||
|
+ // @ts-expect-error
|
||||||
|
+ const isUnsupportedPlatform: boolean = !addRequiredOptions[provider];
|
||||||
|
+
|
||||||
|
+ if (isUnsupportedPlatform) {
|
||||||
|
+ println("WARNING: Platform is not natively supported by the LOM yet!\n");
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ let connectionDetails: string = "";
|
||||||
|
+
|
||||||
|
+ if (options.forceCustomParameters || isUnsupportedPlatform) {
|
||||||
|
+ if (typeof options.customParameters != "string") {
|
||||||
|
+ return println("ERROR: You are missing the custom parameters option!\n");
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ connectionDetails = options.customParameters;
|
||||||
|
+ } else if (provider == "ssh") {
|
||||||
|
+ for (const argument of addRequiredOptions["ssh"]) {
|
||||||
|
+ // No.
|
||||||
|
+ // @ts-expect-error
|
||||||
|
+ const hasArgument = options[argument] as any;
|
||||||
|
+
|
||||||
|
+ if (!hasArgument) {
|
||||||
|
+ return println("ERROR: Missing argument '%s'\n", hasArgument);
|
||||||
|
+ };
|
||||||
|
+ };
|
||||||
|
+
|
||||||
|
+ // todo!
|
||||||
|
+ } else if (provider == "passyfire") {
|
||||||
|
+ for (const argument of addRequiredOptions["passyfire"]) {
|
||||||
|
+ // No.
|
||||||
|
+ // @ts-expect-error
|
||||||
|
+ const hasArgument = options[argument];
|
||||||
|
+
|
||||||
|
+ if (!hasArgument) {
|
||||||
|
+ return println("ERROR: Missing argument '%s'\n", hasArgument);
|
||||||
|
+ };
|
||||||
|
+ };
|
||||||
|
+
|
||||||
|
+ // todo!
|
||||||
|
+ }
|
||||||
|
+ });
|
||||||
|
+
|
||||||
|
const removeBackend = new SSHCommand(println, "rm");
|
||||||
|
removeBackend.description("Removes a backend");
|
||||||
|
removeBackend.argument("<id>", "ID of the backend");
|
||||||
|
@@ -269,7 +341,7 @@ export async function run(
|
||||||
|
|
||||||
|
// It would make sense to check this, then parse argv, however this causes issues with
|
||||||
|
// the application name not displaying correctly.
|
||||||
|
-
|
||||||
|
+
|
||||||
|
if (argv.length == 1) {
|
||||||
|
println("No arguments specified!\n\n");
|
||||||
|
program.help();
|
10
lom/eslint.config.js
Normal file
10
lom/eslint.config.js
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import globals from "globals";
|
||||||
|
import pluginJs from "@eslint/js";
|
||||||
|
import tseslint from "typescript-eslint";
|
||||||
|
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{languageOptions: { globals: globals.node }},
|
||||||
|
pluginJs.configs.recommended,
|
||||||
|
...tseslint.configs.recommended,
|
||||||
|
];
|
1243
lom/package-lock.json
generated
1243
lom/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -14,11 +14,15 @@
|
||||||
"author": "greysoh",
|
"author": "greysoh",
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^9.2.0",
|
||||||
"@types/node": "^20.12.8",
|
"@types/node": "^20.12.8",
|
||||||
"@types/ssh2": "^1.15.0",
|
"@types/ssh2": "^1.15.0",
|
||||||
"@types/yargs": "^17.0.32",
|
"@types/yargs": "^17.0.32",
|
||||||
|
"eslint": "^8.57.0",
|
||||||
|
"globals": "^15.2.0",
|
||||||
"nodemon": "^3.0.3",
|
"nodemon": "^3.0.3",
|
||||||
"typescript": "^5.3.3"
|
"typescript": "^5.3.3",
|
||||||
|
"typescript-eslint": "^7.8.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.6.8",
|
"axios": "^1.6.8",
|
||||||
|
|
|
@ -4,28 +4,22 @@ import { SSHCommand } from "../libs/patchCommander.js";
|
||||||
import type { PrintLine, KeyboardRead } from "../commands.js";
|
import type { PrintLine, KeyboardRead } from "../commands.js";
|
||||||
|
|
||||||
type BackendLookupSuccess = {
|
type BackendLookupSuccess = {
|
||||||
success: boolean,
|
success: boolean;
|
||||||
data: {
|
data: {
|
||||||
id: number,
|
id: number;
|
||||||
|
|
||||||
name: string,
|
name: string;
|
||||||
description: string,
|
description: string;
|
||||||
backend: string,
|
backend: string;
|
||||||
connectionDetails?: string,
|
connectionDetails?: string;
|
||||||
logs: string[]
|
logs: string[];
|
||||||
}[];
|
}[];
|
||||||
};
|
};
|
||||||
|
|
||||||
const addRequiredOptions = {
|
const addRequiredOptions = {
|
||||||
ssh: [
|
ssh: ["sshKey", "username", "host"],
|
||||||
"sshKey",
|
|
||||||
"username",
|
|
||||||
"host",
|
|
||||||
],
|
|
||||||
|
|
||||||
passyfire: [
|
passyfire: ["host"],
|
||||||
"host"
|
|
||||||
]
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function run(
|
export async function run(
|
||||||
|
@ -33,7 +27,7 @@ export async function run(
|
||||||
println: PrintLine,
|
println: PrintLine,
|
||||||
axios: Axios,
|
axios: Axios,
|
||||||
token: string,
|
token: string,
|
||||||
readKeyboard: KeyboardRead
|
readKeyboard: KeyboardRead,
|
||||||
) {
|
) {
|
||||||
const program = new SSHCommand(println);
|
const program = new SSHCommand(println);
|
||||||
program.description("Manages backends for NextNet");
|
program.description("Manages backends for NextNet");
|
||||||
|
@ -103,218 +97,241 @@ export async function run(
|
||||||
"(PassyFire) What password you want to use for the primary user",
|
"(PassyFire) What password you want to use for the primary user",
|
||||||
);
|
);
|
||||||
|
|
||||||
addBackend.action(async(name: string, provider: string, options: {
|
addBackend.action(
|
||||||
description?: string,
|
async (
|
||||||
forceCustomParameters?: boolean,
|
name: string,
|
||||||
customParameters?: string,
|
provider: string,
|
||||||
|
options: {
|
||||||
// SSH (mostly)
|
description?: string;
|
||||||
sshKey?: string,
|
forceCustomParameters?: boolean;
|
||||||
username?: string,
|
customParameters?: string;
|
||||||
host?: string,
|
|
||||||
|
|
||||||
// PassyFire (mostly)
|
// SSH (mostly)
|
||||||
isProxied?: boolean,
|
sshKey?: string;
|
||||||
proxiedPort?: string,
|
username?: string;
|
||||||
guest?: boolean,
|
host?: string;
|
||||||
userAsk?: boolean,
|
|
||||||
password?: string
|
|
||||||
}) => {
|
|
||||||
// Yes it can index for what we need it to do.
|
|
||||||
// @ts-ignore
|
|
||||||
const isUnsupportedPlatform: boolean = !addRequiredOptions[provider];
|
|
||||||
|
|
||||||
if (isUnsupportedPlatform) {
|
|
||||||
println("WARNING: Platform is not natively supported by the LOM yet!\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
let connectionDetails: string = "";
|
// PassyFire (mostly)
|
||||||
|
isProxied?: boolean;
|
||||||
|
proxiedPort?: string;
|
||||||
|
guest?: boolean;
|
||||||
|
userAsk?: boolean;
|
||||||
|
password?: string;
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
// Yes it can index for what we need it to do.
|
||||||
|
// @ts-expect-error
|
||||||
|
const isUnsupportedPlatform: boolean = !addRequiredOptions[provider];
|
||||||
|
|
||||||
if (options.forceCustomParameters || isUnsupportedPlatform) {
|
if (isUnsupportedPlatform) {
|
||||||
if (typeof options.customParameters != "string") {
|
println(
|
||||||
return println("ERROR: You are missing the custom parameters option!\n");
|
"WARNING: Platform is not natively supported by the LOM yet!\n",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
connectionDetails = options.customParameters;
|
let connectionDetails: string = "";
|
||||||
} else if (provider == "ssh") {
|
|
||||||
for (const argument of addRequiredOptions["ssh"]) {
|
|
||||||
// No.
|
|
||||||
// @ts-ignore
|
|
||||||
const hasArgument = options[argument] as any;
|
|
||||||
|
|
||||||
if (!hasArgument) {
|
|
||||||
return println("ERROR: Missing argument '%s'\n", argument);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const unstringifiedArguments: {
|
if (options.forceCustomParameters || isUnsupportedPlatform) {
|
||||||
ip?: string,
|
if (typeof options.customParameters != "string") {
|
||||||
port?: number,
|
return println(
|
||||||
username?: string,
|
"ERROR: You are missing the custom parameters option!\n",
|
||||||
privateKey?: string
|
);
|
||||||
} = {};
|
|
||||||
|
|
||||||
if (options.host) {
|
|
||||||
const sourceSplit: string[] = options.host.split(":");
|
|
||||||
|
|
||||||
const sourceIP: string = sourceSplit[0];
|
|
||||||
const sourcePort: number = sourceSplit.length >= 2 ? parseInt(sourceSplit[1]) : 22;
|
|
||||||
|
|
||||||
unstringifiedArguments.ip = sourceIP;
|
|
||||||
unstringifiedArguments.port = sourcePort;
|
|
||||||
}
|
|
||||||
|
|
||||||
unstringifiedArguments.username = options.username;
|
|
||||||
unstringifiedArguments.privateKey = options.sshKey?.replaceAll("\\n", "\n");
|
|
||||||
|
|
||||||
connectionDetails = JSON.stringify(unstringifiedArguments);
|
|
||||||
} else if (provider == "passyfire") {
|
|
||||||
for (const argument of addRequiredOptions["passyfire"]) {
|
|
||||||
// No.
|
|
||||||
// @ts-ignore
|
|
||||||
const hasArgument = options[argument];
|
|
||||||
|
|
||||||
if (!hasArgument) {
|
|
||||||
return println("ERROR: Missing argument '%s'\n", argument);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const unstringifiedArguments: {
|
|
||||||
ip?: string,
|
|
||||||
port?: number,
|
|
||||||
publicPort?: number,
|
|
||||||
isProxied?: boolean,
|
|
||||||
users: {
|
|
||||||
username: string,
|
|
||||||
password: string
|
|
||||||
}[]
|
|
||||||
} = {
|
|
||||||
users: []
|
|
||||||
};
|
|
||||||
|
|
||||||
if (options.guest) {
|
|
||||||
unstringifiedArguments.users.push({
|
|
||||||
username: "guest",
|
|
||||||
password: "guest"
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
if (options.username) {
|
|
||||||
if (!options.password) {
|
|
||||||
return println("Password must not be left blank\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unstringifiedArguments.users.push({
|
connectionDetails = options.customParameters;
|
||||||
username: options.username,
|
} else if (provider == "ssh") {
|
||||||
password: options.password
|
for (const argument of addRequiredOptions["ssh"]) {
|
||||||
});
|
// No.
|
||||||
};
|
// @ts-expect-error
|
||||||
|
const hasArgument = options[argument] as any;
|
||||||
|
|
||||||
if (options.userAsk) {
|
if (!hasArgument) {
|
||||||
while (true) {
|
return println("ERROR: Missing argument '%s'\n", argument);
|
||||||
println("Creating a user.\nUsername: ");
|
}
|
||||||
const username = await readKeyboard();
|
}
|
||||||
|
|
||||||
let passwordConfirmOne = "a";
|
const unstringifiedArguments: {
|
||||||
let passwordConfirmTwo = "b";
|
ip?: string;
|
||||||
|
port?: number;
|
||||||
|
username?: string;
|
||||||
|
privateKey?: string;
|
||||||
|
} = {};
|
||||||
|
|
||||||
println("\n");
|
if (options.host) {
|
||||||
|
const sourceSplit: string[] = options.host.split(":");
|
||||||
|
|
||||||
while (passwordConfirmOne != passwordConfirmTwo) {
|
const sourceIP: string = sourceSplit[0];
|
||||||
println("Password: ");
|
const sourcePort: number =
|
||||||
passwordConfirmOne = await readKeyboard(true);
|
sourceSplit.length >= 2 ? parseInt(sourceSplit[1]) : 22;
|
||||||
|
|
||||||
println("\nConfirm password: ");
|
|
||||||
passwordConfirmTwo = await readKeyboard(true);
|
|
||||||
|
|
||||||
println("\n");
|
unstringifiedArguments.ip = sourceIP;
|
||||||
|
unstringifiedArguments.port = sourcePort;
|
||||||
if (passwordConfirmOne != passwordConfirmTwo) {
|
}
|
||||||
println("Passwords do not match! Try again.\n\n");
|
|
||||||
}
|
unstringifiedArguments.username = options.username;
|
||||||
|
unstringifiedArguments.privateKey = options.sshKey?.replaceAll(
|
||||||
|
"\\n",
|
||||||
|
"\n",
|
||||||
|
);
|
||||||
|
|
||||||
|
connectionDetails = JSON.stringify(unstringifiedArguments);
|
||||||
|
} else if (provider == "passyfire") {
|
||||||
|
for (const argument of addRequiredOptions["passyfire"]) {
|
||||||
|
// No.
|
||||||
|
// @ts-expect-error
|
||||||
|
const hasArgument = options[argument];
|
||||||
|
|
||||||
|
if (!hasArgument) {
|
||||||
|
return println("ERROR: Missing argument '%s'\n", argument);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const unstringifiedArguments: {
|
||||||
|
ip?: string;
|
||||||
|
port?: number;
|
||||||
|
publicPort?: number;
|
||||||
|
isProxied?: boolean;
|
||||||
|
users: {
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
}[];
|
||||||
|
} = {
|
||||||
|
users: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
if (options.guest) {
|
||||||
|
unstringifiedArguments.users.push({
|
||||||
|
username: "guest",
|
||||||
|
password: "guest",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.username) {
|
||||||
|
if (!options.password) {
|
||||||
|
return println("Password must not be left blank\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
unstringifiedArguments.users.push({
|
unstringifiedArguments.users.push({
|
||||||
username,
|
username: options.username,
|
||||||
password: passwordConfirmOne
|
password: options.password,
|
||||||
});
|
});
|
||||||
|
|
||||||
println("\nShould we continue creating users? (y/n) ");
|
|
||||||
const shouldContinueAsking = (await readKeyboard()).toLowerCase().trim().startsWith("y");
|
|
||||||
|
|
||||||
println("\n\n");
|
|
||||||
|
|
||||||
if (!shouldContinueAsking) break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (unstringifiedArguments.users.length == 0) {
|
|
||||||
return println("No users will be created with your current arguments! You must have users set up.\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
unstringifiedArguments.isProxied = Boolean(options.isProxied);
|
|
||||||
|
|
||||||
if (options.proxiedPort) {
|
|
||||||
unstringifiedArguments.publicPort = parseInt(options.proxiedPort ?? "");
|
|
||||||
|
|
||||||
if (Number.isNaN(unstringifiedArguments.publicPort)) {
|
|
||||||
println("UID (%s) is not a number.\n", options.proxiedPort);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.host) {
|
|
||||||
const sourceSplit: string[] = options.host.split(":");
|
|
||||||
|
|
||||||
if (sourceSplit.length != 2) {
|
|
||||||
return println("Source could not be splitted down (are you missing the ':' in the source to specify port?)\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const sourceIP: string = sourceSplit[0];
|
if (options.userAsk) {
|
||||||
const sourcePort: number = parseInt(sourceSplit[1]);
|
while (true) {
|
||||||
|
println("Creating a user.\nUsername: ");
|
||||||
|
const username = await readKeyboard();
|
||||||
|
|
||||||
if (Number.isNaN(sourcePort)) {
|
let passwordConfirmOne = "a";
|
||||||
println("UID (%s) is not a number.\n", sourcePort);
|
let passwordConfirmTwo = "b";
|
||||||
return;
|
|
||||||
|
println("\n");
|
||||||
|
|
||||||
|
while (passwordConfirmOne != passwordConfirmTwo) {
|
||||||
|
println("Password: ");
|
||||||
|
passwordConfirmOne = await readKeyboard(true);
|
||||||
|
|
||||||
|
println("\nConfirm password: ");
|
||||||
|
passwordConfirmTwo = await readKeyboard(true);
|
||||||
|
|
||||||
|
println("\n");
|
||||||
|
|
||||||
|
if (passwordConfirmOne != passwordConfirmTwo) {
|
||||||
|
println("Passwords do not match! Try again.\n\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unstringifiedArguments.users.push({
|
||||||
|
username,
|
||||||
|
password: passwordConfirmOne,
|
||||||
|
});
|
||||||
|
|
||||||
|
println("\nShould we continue creating users? (y/n) ");
|
||||||
|
const shouldContinueAsking = (await readKeyboard())
|
||||||
|
.toLowerCase()
|
||||||
|
.trim()
|
||||||
|
.startsWith("y");
|
||||||
|
|
||||||
|
println("\n\n");
|
||||||
|
|
||||||
|
if (!shouldContinueAsking) break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unstringifiedArguments.ip = sourceIP;
|
if (unstringifiedArguments.users.length == 0) {
|
||||||
unstringifiedArguments.port = sourcePort;
|
return println(
|
||||||
|
"No users will be created with your current arguments! You must have users set up.\n",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
unstringifiedArguments.isProxied = Boolean(options.isProxied);
|
||||||
|
|
||||||
|
if (options.proxiedPort) {
|
||||||
|
unstringifiedArguments.publicPort = parseInt(
|
||||||
|
options.proxiedPort ?? "",
|
||||||
|
);
|
||||||
|
|
||||||
|
if (Number.isNaN(unstringifiedArguments.publicPort)) {
|
||||||
|
println("UID (%s) is not a number.\n", options.proxiedPort);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.host) {
|
||||||
|
const sourceSplit: string[] = options.host.split(":");
|
||||||
|
|
||||||
|
if (sourceSplit.length != 2) {
|
||||||
|
return println(
|
||||||
|
"Source could not be splitted down (are you missing the ':' in the source to specify port?)\n",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const sourceIP: string = sourceSplit[0];
|
||||||
|
const sourcePort: number = parseInt(sourceSplit[1]);
|
||||||
|
|
||||||
|
if (Number.isNaN(sourcePort)) {
|
||||||
|
println("UID (%s) is not a number.\n", sourcePort);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
unstringifiedArguments.ip = sourceIP;
|
||||||
|
unstringifiedArguments.port = sourcePort;
|
||||||
|
}
|
||||||
|
|
||||||
|
connectionDetails = JSON.stringify(unstringifiedArguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
connectionDetails = JSON.stringify(unstringifiedArguments);
|
const response = await axios.post("/api/v1/backends/create", {
|
||||||
}
|
token,
|
||||||
|
|
||||||
const response = await axios.post("/api/v1/backends/create", {
|
name,
|
||||||
token,
|
description: options.description,
|
||||||
|
backend: provider,
|
||||||
name,
|
|
||||||
description: options.description,
|
|
||||||
backend: provider,
|
|
||||||
|
|
||||||
connectionDetails
|
connectionDetails,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.status != 200) {
|
if (response.status != 200) {
|
||||||
if (process.env.NODE_ENV != "production") console.log(response);
|
if (process.env.NODE_ENV != "production") console.log(response);
|
||||||
|
|
||||||
if (response.data.error) {
|
if (response.data.error) {
|
||||||
println(`Error: ${response.data.error}\n`);
|
println(`Error: ${response.data.error}\n`);
|
||||||
} else {
|
} else {
|
||||||
println("Error creating a backend!\n");
|
println("Error creating a backend!\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
println("Successfully created the backend.\n");
|
||||||
}
|
},
|
||||||
|
);
|
||||||
println("Successfully created the backend.\n");
|
|
||||||
});
|
|
||||||
|
|
||||||
const removeBackend = new SSHCommand(println, "rm");
|
const removeBackend = new SSHCommand(println, "rm");
|
||||||
removeBackend.description("Removes a backend");
|
removeBackend.description("Removes a backend");
|
||||||
removeBackend.argument("<id>", "ID of the backend");
|
removeBackend.argument("<id>", "ID of the backend");
|
||||||
|
|
||||||
removeBackend.action(async(idStr: string) => {
|
removeBackend.action(async (idStr: string) => {
|
||||||
const id: number = parseInt(idStr);
|
const id: number = parseInt(idStr);
|
||||||
|
|
||||||
if (Number.isNaN(id)) {
|
if (Number.isNaN(id)) {
|
||||||
|
@ -324,7 +341,7 @@ export async function run(
|
||||||
|
|
||||||
const response = await axios.post("/api/v1/backends/remove", {
|
const response = await axios.post("/api/v1/backends/remove", {
|
||||||
token,
|
token,
|
||||||
id
|
id,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.status != 200) {
|
if (response.status != 200) {
|
||||||
|
@ -338,7 +355,7 @@ export async function run(
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
println("Backend has been successfully deleted.\n");
|
println("Backend has been successfully deleted.\n");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -359,96 +376,100 @@ export async function run(
|
||||||
|
|
||||||
lookupBackend.option(
|
lookupBackend.option(
|
||||||
"-e, --parse-connection-details",
|
"-e, --parse-connection-details",
|
||||||
"If specified, we automatically parse the connection details to make them human readable, if standard JSON."
|
"If specified, we automatically parse the connection details to make them human readable, if standard JSON.",
|
||||||
);
|
);
|
||||||
|
|
||||||
lookupBackend.action(async(options: {
|
lookupBackend.action(
|
||||||
name?: string,
|
async (options: {
|
||||||
provider?: string,
|
name?: string;
|
||||||
description?: string,
|
provider?: string;
|
||||||
parseConnectionDetails?: boolean
|
description?: string;
|
||||||
}) => {
|
parseConnectionDetails?: boolean;
|
||||||
const response = await axios.post("/api/v1/backends/lookup", {
|
}) => {
|
||||||
token,
|
const response = await axios.post("/api/v1/backends/lookup", {
|
||||||
|
token,
|
||||||
name: options.name,
|
|
||||||
description: options.description,
|
|
||||||
|
|
||||||
backend: options.provider
|
name: options.name,
|
||||||
});
|
description: options.description,
|
||||||
|
|
||||||
if (response.status != 200) {
|
backend: options.provider,
|
||||||
if (process.env.NODE_ENV != "production") console.log(response);
|
});
|
||||||
|
|
||||||
if (response.data.error) {
|
if (response.status != 200) {
|
||||||
println(`Error: ${response.data.error}\n`);
|
if (process.env.NODE_ENV != "production") console.log(response);
|
||||||
} else {
|
|
||||||
println("Error looking up backends!\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
if (response.data.error) {
|
||||||
}
|
println(`Error: ${response.data.error}\n`);
|
||||||
|
|
||||||
const { data }: BackendLookupSuccess = response.data;
|
|
||||||
|
|
||||||
for (const backend of data) {
|
|
||||||
println("ID: %s:\n", backend.id);
|
|
||||||
println(" - Name: %s\n", backend.name);
|
|
||||||
println(" - Description: %s\n", backend.description);
|
|
||||||
println(" - Using Backend: %s\n", backend.backend);
|
|
||||||
|
|
||||||
if (backend.connectionDetails) {
|
|
||||||
if (options.parseConnectionDetails) {
|
|
||||||
// We don't know what we're recieving. We just try to parse it (hence the any type)
|
|
||||||
// {} is more accurate but TS yells at us if we do that :(
|
|
||||||
|
|
||||||
let parsedJSONData: any | undefined;
|
|
||||||
|
|
||||||
try {
|
|
||||||
parsedJSONData = JSON.parse(backend.connectionDetails);
|
|
||||||
} catch (e) {
|
|
||||||
println(" - Connection Details: %s\n", backend.connectionDetails);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!parsedJSONData) {
|
|
||||||
// Not really an assertion but I don't care right now
|
|
||||||
println("Assertion failed: parsedJSONData should not be undefined\n");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
println(" - Connection details:\n");
|
|
||||||
|
|
||||||
for (const key of Object.keys(parsedJSONData)) {
|
|
||||||
let value: string | number = parsedJSONData[key];
|
|
||||||
|
|
||||||
if (typeof value == "string") {
|
|
||||||
value = value.replaceAll("\n", "\n" + " ".repeat(16));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof value == "object") {
|
|
||||||
// TODO: implement?
|
|
||||||
value = JSON.stringify(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
println(" - %s: %s\n", key, value);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
println(" - Connection Details: %s\n", backend.connectionDetails);
|
println("Error looking up backends!\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
println("\n");
|
const { data }: BackendLookupSuccess = response.data;
|
||||||
}
|
|
||||||
|
|
||||||
println("%s backends found.\n", data.length);
|
for (const backend of data) {
|
||||||
});
|
println("ID: %s:\n", backend.id);
|
||||||
|
println(" - Name: %s\n", backend.name);
|
||||||
|
println(" - Description: %s\n", backend.description);
|
||||||
|
println(" - Using Backend: %s\n", backend.backend);
|
||||||
|
|
||||||
|
if (backend.connectionDetails) {
|
||||||
|
if (options.parseConnectionDetails) {
|
||||||
|
// We don't know what we're recieving. We just try to parse it (hence the any type)
|
||||||
|
// {} is more accurate but TS yells at us if we do that :(
|
||||||
|
|
||||||
|
let parsedJSONData: any | undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
parsedJSONData = JSON.parse(backend.connectionDetails);
|
||||||
|
} catch (e) {
|
||||||
|
println(" - Connection Details: %s\n", backend.connectionDetails);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!parsedJSONData) {
|
||||||
|
// Not really an assertion but I don't care right now
|
||||||
|
println(
|
||||||
|
"Assertion failed: parsedJSONData should not be undefined\n",
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
println(" - Connection details:\n");
|
||||||
|
|
||||||
|
for (const key of Object.keys(parsedJSONData)) {
|
||||||
|
let value: string | number = parsedJSONData[key];
|
||||||
|
|
||||||
|
if (typeof value == "string") {
|
||||||
|
value = value.replaceAll("\n", "\n" + " ".repeat(16));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value == "object") {
|
||||||
|
// TODO: implement?
|
||||||
|
value = JSON.stringify(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
println(" - %s: %s\n", key, value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println(" - Connection Details: %s\n", backend.connectionDetails);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
println("%s backends found.\n", data.length);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const logsCommand = new SSHCommand(println, "logs");
|
const logsCommand = new SSHCommand(println, "logs");
|
||||||
logsCommand.description("View logs for a backend");
|
logsCommand.description("View logs for a backend");
|
||||||
logsCommand.argument("<id>", "ID of the backend");
|
logsCommand.argument("<id>", "ID of the backend");
|
||||||
|
|
||||||
logsCommand.action(async(idStr: string) => {
|
logsCommand.action(async (idStr: string) => {
|
||||||
const id: number = parseInt(idStr);
|
const id: number = parseInt(idStr);
|
||||||
|
|
||||||
if (Number.isNaN(id)) {
|
if (Number.isNaN(id)) {
|
||||||
|
@ -458,7 +479,7 @@ export async function run(
|
||||||
|
|
||||||
const response = await axios.post("/api/v1/backends/lookup", {
|
const response = await axios.post("/api/v1/backends/lookup", {
|
||||||
token,
|
token,
|
||||||
id
|
id,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.status != 200) {
|
if (response.status != 200) {
|
||||||
|
@ -474,10 +495,10 @@ export async function run(
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data }: BackendLookupSuccess = response.data;
|
const { data }: BackendLookupSuccess = response.data;
|
||||||
const ourBackend = data.find((i) => i.id == id);
|
const ourBackend = data.find(i => i.id == id);
|
||||||
|
|
||||||
if (!ourBackend) return println("Could not find the backend!\n");
|
if (!ourBackend) return println("Could not find the backend!\n");
|
||||||
ourBackend.logs.forEach((log) => println("%s\n", log));
|
ourBackend.logs.forEach(log => println("%s\n", log));
|
||||||
});
|
});
|
||||||
|
|
||||||
program.addCommand(addBackend);
|
program.addCommand(addBackend);
|
||||||
|
@ -496,5 +517,5 @@ export async function run(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await new Promise((resolve) => program.onExit(resolve));
|
await new Promise(resolve => program.onExit(resolve));
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,36 +5,36 @@ import type { PrintLine } from "../commands.js";
|
||||||
|
|
||||||
// https://stackoverflow.com/questions/37938504/what-is-the-best-way-to-find-all-items-are-deleted-inserted-from-original-arra
|
// https://stackoverflow.com/questions/37938504/what-is-the-best-way-to-find-all-items-are-deleted-inserted-from-original-arra
|
||||||
function difference(a: any[], b: any[]) {
|
function difference(a: any[], b: any[]) {
|
||||||
return a.filter(x => b.indexOf(x) < 0);
|
return a.filter(x => b.indexOf(x) < 0);
|
||||||
};
|
}
|
||||||
|
|
||||||
type InboundConnectionSuccess = {
|
type InboundConnectionSuccess = {
|
||||||
success: true,
|
success: true;
|
||||||
data: {
|
data: {
|
||||||
ip: string,
|
ip: string;
|
||||||
port: number,
|
port: number;
|
||||||
|
|
||||||
connectionDetails: {
|
connectionDetails: {
|
||||||
sourceIP: string,
|
sourceIP: string;
|
||||||
sourcePort: number,
|
sourcePort: number;
|
||||||
destPort: number,
|
destPort: number;
|
||||||
enabled: boolean
|
enabled: boolean;
|
||||||
}
|
};
|
||||||
}[]
|
}[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type LookupCommandSuccess = {
|
type LookupCommandSuccess = {
|
||||||
success: true,
|
success: true;
|
||||||
data: {
|
data: {
|
||||||
id: number,
|
id: number;
|
||||||
name: string,
|
name: string;
|
||||||
description: string,
|
description: string;
|
||||||
sourceIP: string,
|
sourceIP: string;
|
||||||
sourcePort: number,
|
sourcePort: number;
|
||||||
destPort: number,
|
destPort: number;
|
||||||
providerID: number,
|
providerID: number;
|
||||||
autoStart: boolean
|
autoStart: boolean;
|
||||||
}[]
|
}[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function run(
|
export async function run(
|
||||||
|
@ -43,7 +43,11 @@ export async function run(
|
||||||
axios: Axios,
|
axios: Axios,
|
||||||
token: string,
|
token: string,
|
||||||
) {
|
) {
|
||||||
if (argv.length == 1) return println("error: no arguments specified! run %s --help to see commands.\n", argv[0]);
|
if (argv.length == 1)
|
||||||
|
return println(
|
||||||
|
"error: no arguments specified! run %s --help to see commands.\n",
|
||||||
|
argv[0],
|
||||||
|
);
|
||||||
|
|
||||||
const program = new SSHCommand(println);
|
const program = new SSHCommand(println);
|
||||||
program.description("Manages connections for NextNet");
|
program.description("Manages connections for NextNet");
|
||||||
|
@ -68,71 +72,82 @@ export async function run(
|
||||||
addCommand.argument("<dest_port>", "Destination port to use");
|
addCommand.argument("<dest_port>", "Destination port to use");
|
||||||
addCommand.option("-d, --description", "Description for the tunnel");
|
addCommand.option("-d, --description", "Description for the tunnel");
|
||||||
|
|
||||||
addCommand.action(async(providerIDStr: string, name: string, protocolRaw: string, source: string, destPortRaw: string, options: {
|
addCommand.action(
|
||||||
description?: string
|
async (
|
||||||
}) => {
|
providerIDStr: string,
|
||||||
const providerID = parseInt(providerIDStr);
|
name: string,
|
||||||
|
protocolRaw: string,
|
||||||
|
source: string,
|
||||||
|
destPortRaw: string,
|
||||||
|
options: {
|
||||||
|
description?: string;
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
const providerID = parseInt(providerIDStr);
|
||||||
|
|
||||||
if (Number.isNaN(providerID)) {
|
if (Number.isNaN(providerID)) {
|
||||||
println("ID (%s) is not a number\n", providerIDStr);
|
println("ID (%s) is not a number\n", providerIDStr);
|
||||||
return;
|
return;
|
||||||
};
|
|
||||||
|
|
||||||
const protocol = protocolRaw.toLowerCase().trim();
|
|
||||||
|
|
||||||
if (protocol != "tcp" && protocol != "udp") {
|
|
||||||
return println("Protocol is not a valid option (not tcp or udp)\n");
|
|
||||||
};
|
|
||||||
|
|
||||||
const sourceSplit: string[] = source.split(":");
|
|
||||||
|
|
||||||
if (sourceSplit.length != 2) {
|
|
||||||
return println("Source could not be splitted down (are you missing the ':' in the source to specify port?)\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
const sourceIP: string = sourceSplit[0];
|
|
||||||
const sourcePort: number = parseInt(sourceSplit[1]);
|
|
||||||
|
|
||||||
if (Number.isNaN(sourcePort)) {
|
|
||||||
return println("Port splitted is not a number\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
const destinationPort: number = parseInt(destPortRaw);
|
|
||||||
|
|
||||||
if (Number.isNaN(destinationPort)) {
|
|
||||||
return println("Destination port could not be parsed into a number\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await axios.post("/api/v1/forward/create", {
|
|
||||||
token,
|
|
||||||
|
|
||||||
name,
|
|
||||||
description: options.description,
|
|
||||||
|
|
||||||
protocol,
|
|
||||||
|
|
||||||
sourceIP,
|
|
||||||
sourcePort,
|
|
||||||
|
|
||||||
destinationPort,
|
|
||||||
|
|
||||||
providerID
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.status != 200) {
|
|
||||||
if (process.env.NODE_ENV != "production") console.log(response);
|
|
||||||
|
|
||||||
if (response.data.error) {
|
|
||||||
println(`Error: ${response.data.error}\n`);
|
|
||||||
} else {
|
|
||||||
println("Error creating a connection!\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
const protocol = protocolRaw.toLowerCase().trim();
|
||||||
}
|
|
||||||
|
|
||||||
println("Successfully created connection.\n");
|
if (protocol != "tcp" && protocol != "udp") {
|
||||||
});
|
return println("Protocol is not a valid option (not tcp or udp)\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
const sourceSplit: string[] = source.split(":");
|
||||||
|
|
||||||
|
if (sourceSplit.length != 2) {
|
||||||
|
return println(
|
||||||
|
"Source could not be splitted down (are you missing the ':' in the source to specify port?)\n",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const sourceIP: string = sourceSplit[0];
|
||||||
|
const sourcePort: number = parseInt(sourceSplit[1]);
|
||||||
|
|
||||||
|
if (Number.isNaN(sourcePort)) {
|
||||||
|
return println("Port splitted is not a number\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
const destinationPort: number = parseInt(destPortRaw);
|
||||||
|
|
||||||
|
if (Number.isNaN(destinationPort)) {
|
||||||
|
return println("Destination port could not be parsed into a number\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await axios.post("/api/v1/forward/create", {
|
||||||
|
token,
|
||||||
|
|
||||||
|
name,
|
||||||
|
description: options.description,
|
||||||
|
|
||||||
|
protocol,
|
||||||
|
|
||||||
|
sourceIP,
|
||||||
|
sourcePort,
|
||||||
|
|
||||||
|
destinationPort,
|
||||||
|
|
||||||
|
providerID,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.status != 200) {
|
||||||
|
if (process.env.NODE_ENV != "production") console.log(response);
|
||||||
|
|
||||||
|
if (response.data.error) {
|
||||||
|
println(`Error: ${response.data.error}\n`);
|
||||||
|
} else {
|
||||||
|
println("Error creating a connection!\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
println("Successfully created connection.\n");
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const lookupCommand = new SSHCommand(println, "find");
|
const lookupCommand = new SSHCommand(println, "find");
|
||||||
|
|
||||||
|
@ -164,111 +179,124 @@ export async function run(
|
||||||
"Description for the tunnel",
|
"Description for the tunnel",
|
||||||
);
|
);
|
||||||
|
|
||||||
lookupCommand.action(async(options: {
|
lookupCommand.action(
|
||||||
backendId?: string,
|
async (options: {
|
||||||
destPort?: string,
|
backendId?: string;
|
||||||
name?: string,
|
destPort?: string;
|
||||||
protocol?: string,
|
name?: string;
|
||||||
source?: string,
|
protocol?: string;
|
||||||
description?: string
|
source?: string;
|
||||||
}) => {
|
description?: string;
|
||||||
let numberBackendID: number | undefined;
|
}) => {
|
||||||
|
let numberBackendID: number | undefined;
|
||||||
|
|
||||||
let sourceIP: string | undefined;
|
let sourceIP: string | undefined;
|
||||||
let sourcePort: number | undefined;
|
let sourcePort: number | undefined;
|
||||||
|
|
||||||
let destPort: number | undefined;
|
let destPort: number | undefined;
|
||||||
|
|
||||||
if (options.backendId) {
|
if (options.backendId) {
|
||||||
numberBackendID = parseInt(options.backendId);
|
numberBackendID = parseInt(options.backendId);
|
||||||
|
|
||||||
|
if (Number.isNaN(numberBackendID)) {
|
||||||
|
println("ID (%s) is not a number\n", options.backendId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.source) {
|
||||||
|
const sourceSplit: string[] = options.source.split(":");
|
||||||
|
|
||||||
|
if (sourceSplit.length != 2) {
|
||||||
|
return println(
|
||||||
|
"Source could not be splitted down (are you missing the ':' in the source to specify port?)\n",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceIP = sourceSplit[0];
|
||||||
|
sourcePort = parseInt(sourceSplit[1]);
|
||||||
|
|
||||||
|
if (Number.isNaN(sourcePort)) {
|
||||||
|
return println("Port splitted is not a number\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.destPort) {
|
||||||
|
destPort = parseInt(options.destPort);
|
||||||
|
|
||||||
|
if (Number.isNaN(destPort)) {
|
||||||
|
println("ID (%s) is not a number\n", options.destPort);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await axios.post("/api/v1/forward/lookup", {
|
||||||
|
token,
|
||||||
|
|
||||||
|
name: options.name,
|
||||||
|
description: options.description,
|
||||||
|
|
||||||
|
protocol: options.protocol,
|
||||||
|
|
||||||
|
sourceIP,
|
||||||
|
sourcePort,
|
||||||
|
|
||||||
|
destinationPort: destPort,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.status != 200) {
|
||||||
|
if (process.env.NODE_ENV != "production") console.log(response);
|
||||||
|
|
||||||
|
if (response.data.error) {
|
||||||
|
println(`Error: ${response.data.error}\n`);
|
||||||
|
} else {
|
||||||
|
println("Error requesting connections!\n");
|
||||||
|
}
|
||||||
|
|
||||||
if (Number.isNaN(numberBackendID)) {
|
|
||||||
println("ID (%s) is not a number\n", options.backendId);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (options.source) {
|
const { data }: LookupCommandSuccess = response.data;
|
||||||
const sourceSplit: string[] = options.source.split(":");
|
|
||||||
|
|
||||||
if (sourceSplit.length != 2) {
|
for (const connection of data) {
|
||||||
return println("Source could not be splitted down (are you missing the ':' in the source to specify port?)\n");
|
println(
|
||||||
}
|
"ID: %s%s:\n",
|
||||||
|
connection.id,
|
||||||
sourceIP = sourceSplit[0];
|
connection.autoStart ? " (automatically starts)" : "",
|
||||||
sourcePort = parseInt(sourceSplit[1]);
|
);
|
||||||
|
println(" - Backend ID: %s\n", connection.providerID);
|
||||||
if (Number.isNaN(sourcePort)) {
|
println(" - Name: %s\n", connection.name);
|
||||||
return println("Port splitted is not a number\n");
|
if (connection.description)
|
||||||
}
|
println(" - Description: %s\n", connection.description);
|
||||||
}
|
println(
|
||||||
|
" - Source: %s:%s\n",
|
||||||
|
connection.sourceIP,
|
||||||
|
connection.sourcePort,
|
||||||
|
);
|
||||||
|
println(" - Destination port: %s\n", connection.destPort);
|
||||||
|
|
||||||
if (options.destPort) {
|
println("\n");
|
||||||
destPort = parseInt(options.destPort);
|
|
||||||
|
|
||||||
if (Number.isNaN(destPort)) {
|
|
||||||
println("ID (%s) is not a number\n", options.destPort);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await axios.post("/api/v1/forward/lookup", {
|
|
||||||
token,
|
|
||||||
|
|
||||||
name: options.name,
|
|
||||||
description: options.description,
|
|
||||||
|
|
||||||
protocol: options.protocol,
|
|
||||||
|
|
||||||
sourceIP,
|
|
||||||
sourcePort,
|
|
||||||
|
|
||||||
destinationPort: destPort
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.status != 200) {
|
|
||||||
if (process.env.NODE_ENV != "production") console.log(response);
|
|
||||||
|
|
||||||
if (response.data.error) {
|
|
||||||
println(`Error: ${response.data.error}\n`);
|
|
||||||
} else {
|
|
||||||
println("Error requesting connections!\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
println("%s connections found.\n", data.length);
|
||||||
}
|
},
|
||||||
|
);
|
||||||
const { data }: LookupCommandSuccess = response.data;
|
|
||||||
|
|
||||||
for (const connection of data) {
|
|
||||||
println("ID: %s%s:\n", connection.id, (connection.autoStart ? " (automatically starts)" : ""));
|
|
||||||
println(" - Backend ID: %s\n", connection.providerID);
|
|
||||||
println(" - Name: %s\n", connection.name);
|
|
||||||
if (connection.description) println(" - Description: %s\n", connection.description);
|
|
||||||
println(" - Source: %s:%s\n", connection.sourceIP, connection.sourcePort);
|
|
||||||
println(" - Destination port: %s\n", connection.destPort);
|
|
||||||
|
|
||||||
println("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
println("%s connections found.\n", data.length);
|
|
||||||
});
|
|
||||||
|
|
||||||
const startTunnel = new SSHCommand(println, "start");
|
const startTunnel = new SSHCommand(println, "start");
|
||||||
startTunnel.description("Starts a tunnel");
|
startTunnel.description("Starts a tunnel");
|
||||||
startTunnel.argument("<id>", "Tunnel ID to start");
|
startTunnel.argument("<id>", "Tunnel ID to start");
|
||||||
|
|
||||||
startTunnel.action(async(idStr: string) => {
|
startTunnel.action(async (idStr: string) => {
|
||||||
const id = parseInt(idStr);
|
const id = parseInt(idStr);
|
||||||
|
|
||||||
if (Number.isNaN(id)) {
|
if (Number.isNaN(id)) {
|
||||||
println("ID (%s) is not a number\n", idStr);
|
println("ID (%s) is not a number\n", idStr);
|
||||||
return;
|
return;
|
||||||
};
|
}
|
||||||
|
|
||||||
const response = await axios.post("/api/v1/forward/start", {
|
const response = await axios.post("/api/v1/forward/start", {
|
||||||
token,
|
token,
|
||||||
id
|
id,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.status != 200) {
|
if (response.status != 200) {
|
||||||
|
@ -291,17 +319,17 @@ export async function run(
|
||||||
stopTunnel.description("Stops a tunnel");
|
stopTunnel.description("Stops a tunnel");
|
||||||
stopTunnel.argument("<id>", "Tunnel ID to stop");
|
stopTunnel.argument("<id>", "Tunnel ID to stop");
|
||||||
|
|
||||||
stopTunnel.action(async(idStr: string) => {
|
stopTunnel.action(async (idStr: string) => {
|
||||||
const id = parseInt(idStr);
|
const id = parseInt(idStr);
|
||||||
|
|
||||||
if (Number.isNaN(id)) {
|
if (Number.isNaN(id)) {
|
||||||
println("ID (%s) is not a number\n", idStr);
|
println("ID (%s) is not a number\n", idStr);
|
||||||
return;
|
return;
|
||||||
};
|
}
|
||||||
|
|
||||||
const response = await axios.post("/api/v1/forward/stop", {
|
const response = await axios.post("/api/v1/forward/stop", {
|
||||||
token,
|
token,
|
||||||
id
|
id,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.status != 200) {
|
if (response.status != 200) {
|
||||||
|
@ -323,107 +351,127 @@ export async function run(
|
||||||
getInbound.description("Shows all current connections");
|
getInbound.description("Shows all current connections");
|
||||||
getInbound.argument("<id>", "Tunnel ID to view inbound connections of");
|
getInbound.argument("<id>", "Tunnel ID to view inbound connections of");
|
||||||
getInbound.option("-t, --tail", "Live-view of connection list");
|
getInbound.option("-t, --tail", "Live-view of connection list");
|
||||||
getInbound.option("-s, --tail-pull-rate <ms>", "Controls the speed to pull at (in ms)");
|
getInbound.option(
|
||||||
|
"-s, --tail-pull-rate <ms>",
|
||||||
|
"Controls the speed to pull at (in ms)",
|
||||||
|
);
|
||||||
|
|
||||||
getInbound.action(async(idStr: string, options: {
|
getInbound.action(
|
||||||
tail?: boolean,
|
async (
|
||||||
tailPullRate?: string
|
idStr: string,
|
||||||
}): Promise<void> => {
|
options: {
|
||||||
const pullRate: number = options.tailPullRate ? parseInt(options.tailPullRate) : 2000;
|
tail?: boolean;
|
||||||
const id = parseInt(idStr);
|
tailPullRate?: string;
|
||||||
|
},
|
||||||
|
): Promise<void> => {
|
||||||
|
const pullRate: number = options.tailPullRate
|
||||||
|
? parseInt(options.tailPullRate)
|
||||||
|
: 2000;
|
||||||
|
const id = parseInt(idStr);
|
||||||
|
|
||||||
if (Number.isNaN(id)) {
|
if (Number.isNaN(id)) {
|
||||||
println("ID (%s) is not a number\n", idStr);
|
println("ID (%s) is not a number\n", idStr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Number.isNaN(pullRate)) {
|
if (Number.isNaN(pullRate)) {
|
||||||
println("Pull rate is not a number\n");
|
println("Pull rate is not a number\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.tail) {
|
if (options.tail) {
|
||||||
let previousEntries: string[] = [];
|
let previousEntries: string[] = [];
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
|
const response = await axios.post("/api/v1/forward/connections", {
|
||||||
|
token,
|
||||||
|
id,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.status != 200) {
|
||||||
|
if (process.env.NODE_ENV != "production") console.log(response);
|
||||||
|
|
||||||
|
if (response.data.error) {
|
||||||
|
println(`Error: ${response.data.error}\n`);
|
||||||
|
} else {
|
||||||
|
println("Error requesting inbound connections!\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data }: InboundConnectionSuccess = response.data;
|
||||||
|
const simplifiedArray: string[] = data.map(i => `${i.ip}:${i.port}`);
|
||||||
|
|
||||||
|
const insertedItems: string[] = difference(
|
||||||
|
simplifiedArray,
|
||||||
|
previousEntries,
|
||||||
|
);
|
||||||
|
const removedItems: string[] = difference(
|
||||||
|
previousEntries,
|
||||||
|
simplifiedArray,
|
||||||
|
);
|
||||||
|
|
||||||
|
insertedItems.forEach(i => println("CONNECTED: %s\n", i));
|
||||||
|
removedItems.forEach(i => println("DISCONNECTED: %s\n", i));
|
||||||
|
|
||||||
|
previousEntries = simplifiedArray;
|
||||||
|
|
||||||
|
await new Promise(i => setTimeout(i, pullRate));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
const response = await axios.post("/api/v1/forward/connections", {
|
const response = await axios.post("/api/v1/forward/connections", {
|
||||||
token,
|
token,
|
||||||
id
|
id,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.status != 200) {
|
if (response.status != 200) {
|
||||||
if (process.env.NODE_ENV != "production") console.log(response);
|
if (process.env.NODE_ENV != "production") console.log(response);
|
||||||
|
|
||||||
if (response.data.error) {
|
if (response.data.error) {
|
||||||
println(`Error: ${response.data.error}\n`);
|
println(`Error: ${response.data.error}\n`);
|
||||||
} else {
|
} else {
|
||||||
println("Error requesting inbound connections!\n");
|
println("Error requesting connections!\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data }: InboundConnectionSuccess = response.data;
|
const { data }: InboundConnectionSuccess = response.data;
|
||||||
const simplifiedArray: string[] = data.map((i) => `${i.ip}:${i.port}`);
|
|
||||||
|
|
||||||
const insertedItems: string[] = difference(simplifiedArray, previousEntries);
|
if (data.length == 0) {
|
||||||
const removedItems: string[] = difference(previousEntries, simplifiedArray);
|
println("There are currently no connected clients.\n");
|
||||||
|
return;
|
||||||
insertedItems.forEach((i) => println("CONNECTED: %s\n", i));
|
|
||||||
removedItems.forEach((i) => println("DISCONNECTED: %s\n", i));
|
|
||||||
|
|
||||||
previousEntries = simplifiedArray;
|
|
||||||
|
|
||||||
await new Promise((i) => setTimeout(i, pullRate));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const response = await axios.post("/api/v1/forward/connections", {
|
|
||||||
token,
|
|
||||||
id
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.status != 200) {
|
|
||||||
if (process.env.NODE_ENV != "production") console.log(response);
|
|
||||||
|
|
||||||
if (response.data.error) {
|
|
||||||
println(`Error: ${response.data.error}\n`);
|
|
||||||
} else {
|
|
||||||
println("Error requesting connections!\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
println(
|
||||||
|
"Connected clients (for source: %s:%s):\n",
|
||||||
|
data[0].connectionDetails.sourceIP,
|
||||||
|
data[0].connectionDetails.sourcePort,
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const entry of data) {
|
||||||
|
println(" - %s:%s\n", entry.ip, entry.port);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
const { data }: InboundConnectionSuccess = response.data;
|
);
|
||||||
|
|
||||||
if (data.length == 0) {
|
|
||||||
println("There are currently no connected clients.\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
println("Connected clients (for source: %s:%s):\n", data[0].connectionDetails.sourceIP, data[0].connectionDetails.sourcePort);
|
|
||||||
|
|
||||||
for (const entry of data) {
|
|
||||||
println(" - %s:%s\n", entry.ip, entry.port);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const removeTunnel = new SSHCommand(println, "rm");
|
const removeTunnel = new SSHCommand(println, "rm");
|
||||||
removeTunnel.description("Removes a tunnel");
|
removeTunnel.description("Removes a tunnel");
|
||||||
removeTunnel.argument("<id>", "Tunnel ID to remove");
|
removeTunnel.argument("<id>", "Tunnel ID to remove");
|
||||||
|
|
||||||
removeTunnel.action(async(idStr: string) => {
|
removeTunnel.action(async (idStr: string) => {
|
||||||
const id = parseInt(idStr);
|
const id = parseInt(idStr);
|
||||||
|
|
||||||
if (Number.isNaN(id)) {
|
if (Number.isNaN(id)) {
|
||||||
println("ID (%s) is not a number\n", idStr);
|
println("ID (%s) is not a number\n", idStr);
|
||||||
return;
|
return;
|
||||||
};
|
}
|
||||||
|
|
||||||
const response = await axios.post("/api/v1/forward/remove", {
|
const response = await axios.post("/api/v1/forward/remove", {
|
||||||
token,
|
token,
|
||||||
id
|
id,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.status != 200) {
|
if (response.status != 200) {
|
||||||
|
@ -436,7 +484,7 @@ export async function run(
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
};
|
}
|
||||||
|
|
||||||
println("Successfully deleted connection.\n");
|
println("Successfully deleted connection.\n");
|
||||||
});
|
});
|
||||||
|
@ -449,5 +497,5 @@ export async function run(
|
||||||
program.addCommand(removeTunnel);
|
program.addCommand(removeTunnel);
|
||||||
|
|
||||||
program.parse(argv);
|
program.parse(argv);
|
||||||
await new Promise((resolve) => program.onExit(resolve));
|
await new Promise(resolve => program.onExit(resolve));
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,11 +6,11 @@ import type { PrintLine, KeyboardRead } from "../commands.js";
|
||||||
type UserLookupSuccess = {
|
type UserLookupSuccess = {
|
||||||
success: true;
|
success: true;
|
||||||
data: {
|
data: {
|
||||||
id: number,
|
id: number;
|
||||||
isServiceAccount: boolean,
|
isServiceAccount: boolean;
|
||||||
username: string,
|
username: string;
|
||||||
name: string,
|
name: string;
|
||||||
email: string
|
email: string;
|
||||||
}[];
|
}[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -19,9 +19,13 @@ export async function run(
|
||||||
println: PrintLine,
|
println: PrintLine,
|
||||||
axios: Axios,
|
axios: Axios,
|
||||||
apiKey: string,
|
apiKey: string,
|
||||||
readKeyboard: KeyboardRead
|
readKeyboard: KeyboardRead,
|
||||||
) {
|
) {
|
||||||
if (argv.length == 1) return println("error: no arguments specified! run %s --help to see commands.\n", argv[0]);
|
if (argv.length == 1)
|
||||||
|
return println(
|
||||||
|
"error: no arguments specified! run %s --help to see commands.\n",
|
||||||
|
argv[0],
|
||||||
|
);
|
||||||
|
|
||||||
const program = new SSHCommand(println);
|
const program = new SSHCommand(println);
|
||||||
program.description("Manages users for NextNet");
|
program.description("Manages users for NextNet");
|
||||||
|
@ -39,69 +43,76 @@ export async function run(
|
||||||
"Asks for a password. Hides output",
|
"Asks for a password. Hides output",
|
||||||
);
|
);
|
||||||
|
|
||||||
addCommand.action(async(username: string, email: string, name: string, options: {
|
addCommand.action(
|
||||||
password?: string,
|
async (
|
||||||
askPassword?: boolean
|
username: string,
|
||||||
}) => {
|
email: string,
|
||||||
if (!options.password && !options.askPassword) {
|
name: string,
|
||||||
println("No password supplied, and askpass has not been supplied.\n");
|
options: {
|
||||||
return;
|
password?: string;
|
||||||
};
|
askPassword?: boolean;
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
if (!options.password && !options.askPassword) {
|
||||||
|
println("No password supplied, and askpass has not been supplied.\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let password: string = "";
|
let password: string = "";
|
||||||
|
|
||||||
if (options.askPassword) {
|
if (options.askPassword) {
|
||||||
let passwordConfirmOne = "a";
|
let passwordConfirmOne = "a";
|
||||||
let passwordConfirmTwo = "b";
|
let passwordConfirmTwo = "b";
|
||||||
|
|
||||||
while (passwordConfirmOne != passwordConfirmTwo) {
|
while (passwordConfirmOne != passwordConfirmTwo) {
|
||||||
println("Password: ");
|
println("Password: ");
|
||||||
passwordConfirmOne = await readKeyboard(true);
|
passwordConfirmOne = await readKeyboard(true);
|
||||||
|
|
||||||
println("\nConfirm password: ");
|
|
||||||
passwordConfirmTwo = await readKeyboard(true);
|
|
||||||
|
|
||||||
println("\n");
|
println("\nConfirm password: ");
|
||||||
|
passwordConfirmTwo = await readKeyboard(true);
|
||||||
|
|
||||||
if (passwordConfirmOne != passwordConfirmTwo) {
|
println("\n");
|
||||||
println("Passwords do not match! Try again.\n\n");
|
|
||||||
|
if (passwordConfirmOne != passwordConfirmTwo) {
|
||||||
|
println("Passwords do not match! Try again.\n\n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
password = passwordConfirmOne;
|
password = passwordConfirmOne;
|
||||||
} else {
|
|
||||||
// From the first check we do, we know this is safe (you MUST specify a password)
|
|
||||||
// @ts-ignore
|
|
||||||
password = options.password;
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await axios.post("/api/v1/users/create", {
|
|
||||||
name,
|
|
||||||
username,
|
|
||||||
email,
|
|
||||||
password
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.status != 200) {
|
|
||||||
if (process.env.NODE_ENV != "production") console.log(response);
|
|
||||||
|
|
||||||
if (response.data.error) {
|
|
||||||
println(`Error: ${response.data.error}\n`);
|
|
||||||
} else {
|
} else {
|
||||||
println("Error creating users!\n");
|
// From the first check we do, we know this is safe (you MUST specify a password)
|
||||||
|
// @ts-expect-error
|
||||||
|
password = options.password;
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
const response = await axios.post("/api/v1/users/create", {
|
||||||
}
|
name,
|
||||||
|
username,
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
});
|
||||||
|
|
||||||
println("User created successfully.\n");
|
if (response.status != 200) {
|
||||||
})
|
if (process.env.NODE_ENV != "production") console.log(response);
|
||||||
|
|
||||||
|
if (response.data.error) {
|
||||||
|
println(`Error: ${response.data.error}\n`);
|
||||||
|
} else {
|
||||||
|
println("Error creating users!\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
println("User created successfully.\n");
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const removeCommand = new SSHCommand(println, "rm");
|
const removeCommand = new SSHCommand(println, "rm");
|
||||||
removeCommand.description("Remove a user");
|
removeCommand.description("Remove a user");
|
||||||
removeCommand.argument("<uid>", "ID of user to remove");
|
removeCommand.argument("<uid>", "ID of user to remove");
|
||||||
|
|
||||||
removeCommand.action(async(uidStr: string) => {
|
removeCommand.action(async (uidStr: string) => {
|
||||||
const uid = parseInt(uidStr);
|
const uid = parseInt(uidStr);
|
||||||
|
|
||||||
if (Number.isNaN(uid)) {
|
if (Number.isNaN(uid)) {
|
||||||
|
@ -111,7 +122,7 @@ export async function run(
|
||||||
|
|
||||||
let response = await axios.post("/api/v1/users/remove", {
|
let response = await axios.post("/api/v1/users/remove", {
|
||||||
token: apiKey,
|
token: apiKey,
|
||||||
uid
|
uid,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.status != 200) {
|
if (response.status != 200) {
|
||||||
|
@ -125,7 +136,7 @@ export async function run(
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
println("User has been successfully deleted.\n");
|
println("User has been successfully deleted.\n");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -137,7 +148,7 @@ export async function run(
|
||||||
lookupCommand.option("-e, --email <email>", "Email of User");
|
lookupCommand.option("-e, --email <email>", "Email of User");
|
||||||
lookupCommand.option("-s, --service", "The user is a service account");
|
lookupCommand.option("-s, --service", "The user is a service account");
|
||||||
|
|
||||||
lookupCommand.action(async(options) => {
|
lookupCommand.action(async options => {
|
||||||
// FIXME: redundant parseInt calls
|
// FIXME: redundant parseInt calls
|
||||||
|
|
||||||
if (options.id) {
|
if (options.id) {
|
||||||
|
@ -146,7 +157,7 @@ export async function run(
|
||||||
if (Number.isNaN(uid)) {
|
if (Number.isNaN(uid)) {
|
||||||
println("UID (%s) is not a number.\n", uid);
|
println("UID (%s) is not a number.\n", uid);
|
||||||
return;
|
return;
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await axios.post("/api/v1/users/lookup", {
|
const response = await axios.post("/api/v1/users/lookup", {
|
||||||
|
@ -155,7 +166,7 @@ export async function run(
|
||||||
name: options.name,
|
name: options.name,
|
||||||
username: options.username,
|
username: options.username,
|
||||||
email: options.email,
|
email: options.email,
|
||||||
service: Boolean(options.service)
|
service: Boolean(options.service),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.status != 200) {
|
if (response.status != 200) {
|
||||||
|
@ -173,7 +184,11 @@ export async function run(
|
||||||
const { data }: UserLookupSuccess = response.data;
|
const { data }: UserLookupSuccess = response.data;
|
||||||
|
|
||||||
for (const user of data) {
|
for (const user of data) {
|
||||||
println("UID: %s%s:\n", user.id, (user.isServiceAccount ? " (service)" : ""));
|
println(
|
||||||
|
"UID: %s%s:\n",
|
||||||
|
user.id,
|
||||||
|
user.isServiceAccount ? " (service)" : "",
|
||||||
|
);
|
||||||
println("- Username: %s\n", user.username);
|
println("- Username: %s\n", user.username);
|
||||||
println("- Name: %s\n", user.name);
|
println("- Name: %s\n", user.name);
|
||||||
println("- Email: %s\n", user.email);
|
println("- Email: %s\n", user.email);
|
||||||
|
@ -182,12 +197,12 @@ export async function run(
|
||||||
}
|
}
|
||||||
|
|
||||||
println("%s users found.\n", data.length);
|
println("%s users found.\n", data.length);
|
||||||
})
|
});
|
||||||
|
|
||||||
program.addCommand(addCommand);
|
program.addCommand(addCommand);
|
||||||
program.addCommand(removeCommand);
|
program.addCommand(removeCommand);
|
||||||
program.addCommand(lookupCommand);
|
program.addCommand(lookupCommand);
|
||||||
|
|
||||||
program.parse(argv);
|
program.parse(argv);
|
||||||
await new Promise((resolve) => program.onExit(resolve));
|
await new Promise(resolve => program.onExit(resolve));
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,22 +53,22 @@ export class SSHCommand extends Command {
|
||||||
|
|
||||||
recvExitDispatch() {
|
recvExitDispatch() {
|
||||||
this.hasRecievedExitSignal = true;
|
this.hasRecievedExitSignal = true;
|
||||||
this.exitEventHandlers.forEach((eventHandler) => eventHandler());
|
this.exitEventHandlers.forEach(eventHandler => eventHandler());
|
||||||
|
|
||||||
let parentElement = this.parent;
|
let parentElement = this.parent;
|
||||||
|
|
||||||
while (parentElement instanceof SSHCommand) {
|
while (parentElement instanceof SSHCommand) {
|
||||||
parentElement.hasRecievedExitSignal = true;
|
parentElement.hasRecievedExitSignal = true;
|
||||||
parentElement.exitEventHandlers.forEach((eventHandler) => eventHandler());
|
parentElement.exitEventHandlers.forEach(eventHandler => eventHandler());
|
||||||
|
|
||||||
parentElement = parentElement.parent;
|
parentElement = parentElement.parent;
|
||||||
};
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
onExit(callback: (...any: any[]) => void) {
|
onExit(callback: (...any: any[]) => void) {
|
||||||
this.exitEventHandlers.push(callback);
|
this.exitEventHandlers.push(callback);
|
||||||
if (this.hasRecievedExitSignal) callback();
|
if (this.hasRecievedExitSignal) callback();
|
||||||
};
|
}
|
||||||
|
|
||||||
_exit() {
|
_exit() {
|
||||||
this.recvExitDispatch();
|
this.recvExitDispatch();
|
||||||
|
@ -81,11 +81,11 @@ export class SSHCommand extends Command {
|
||||||
action(fn: (...args: any[]) => void | Promise<void>): this {
|
action(fn: (...args: any[]) => void | Promise<void>): this {
|
||||||
super.action(fn);
|
super.action(fn);
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-expect-error
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
const oldActionHandler: (...args: any[]) => void | Promise<void> = this._actionHandler;
|
const oldActionHandler: (...args: any[]) => void | Promise<void> = this._actionHandler;
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-expect-error
|
||||||
this._actionHandler = async (...args: any[]): Promise<void> => {
|
this._actionHandler = async (...args: any[]): Promise<void> => {
|
||||||
if (this.hasRecievedExitSignal) return;
|
if (this.hasRecievedExitSignal) return;
|
||||||
await oldActionHandler(...args);
|
await oldActionHandler(...args);
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import type { ServerChannel } from "ssh2";
|
import type { ServerChannel } from "ssh2";
|
||||||
|
|
||||||
const pullRate = process.env.KEYBOARD_PULLING_RATE ? parseInt(process.env.KEYBOARD_PULLING_RATE) : 5;
|
const pullRate = process.env.KEYBOARD_PULLING_RATE
|
||||||
|
? parseInt(process.env.KEYBOARD_PULLING_RATE)
|
||||||
|
: 5;
|
||||||
|
|
||||||
const leftEscape = "\x1B[D";
|
const leftEscape = "\x1B[D";
|
||||||
const rightEscape = "\x1B[C";
|
const rightEscape = "\x1B[C";
|
||||||
|
@ -33,19 +35,20 @@ export async function readFromKeyboard(
|
||||||
} else if (character == clientBackspace) {
|
} else if (character == clientBackspace) {
|
||||||
if (line.length == 0) return setTimeout(eventLoop, pullRate); // Here because if we do it in the parent if statement, shit breaks
|
if (line.length == 0) return setTimeout(eventLoop, pullRate); // Here because if we do it in the parent if statement, shit breaks
|
||||||
line = line.substring(0, lineIndex - 1) + line.substring(lineIndex);
|
line = line.substring(0, lineIndex - 1) + line.substring(lineIndex);
|
||||||
|
|
||||||
if (!disableEcho) {
|
if (!disableEcho) {
|
||||||
const deltaCursor = line.length - lineIndex;
|
const deltaCursor = line.length - lineIndex;
|
||||||
|
|
||||||
if (deltaCursor == line.length) return setTimeout(eventLoop, pullRate);
|
if (deltaCursor == line.length)
|
||||||
|
return setTimeout(eventLoop, pullRate);
|
||||||
|
|
||||||
if (deltaCursor < 0) {
|
if (deltaCursor < 0) {
|
||||||
// Use old technique if the delta is < 0, as the new one is tailored to the start + 1 to end - 1
|
// Use old technique if the delta is < 0, as the new one is tailored to the start + 1 to end - 1
|
||||||
stream.write(ourBackspace + " " + ourBackspace);
|
stream.write(ourBackspace + " " + ourBackspace);
|
||||||
} else {
|
} else {
|
||||||
// Jump forward to the front, and remove the last character
|
// Jump forward to the front, and remove the last character
|
||||||
stream.write(rightEscape.repeat(deltaCursor) + " " + ourBackspace);
|
stream.write(rightEscape.repeat(deltaCursor) + " " + ourBackspace);
|
||||||
|
|
||||||
// Go backwards & rerender text & go backwards again (wtf?)
|
// Go backwards & rerender text & go backwards again (wtf?)
|
||||||
stream.write(
|
stream.write(
|
||||||
leftEscape.repeat(deltaCursor + 1) +
|
leftEscape.repeat(deltaCursor + 1) +
|
||||||
|
@ -53,12 +56,13 @@ export async function readFromKeyboard(
|
||||||
leftEscape.repeat(deltaCursor + 1),
|
leftEscape.repeat(deltaCursor + 1),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
lineIndex -= 1;
|
lineIndex -= 1;
|
||||||
}
|
}
|
||||||
} else if (character == "\x1B") {
|
} else if (character == "\x1B") {
|
||||||
if (character == rightEscape) {
|
if (character == rightEscape) {
|
||||||
if (lineIndex + 1 > line.length) return setTimeout(eventLoop, pullRate);
|
if (lineIndex + 1 > line.length)
|
||||||
|
return setTimeout(eventLoop, pullRate);
|
||||||
lineIndex += 1;
|
lineIndex += 1;
|
||||||
} else if (character == leftEscape) {
|
} else if (character == leftEscape) {
|
||||||
if (lineIndex - 1 < 0) return setTimeout(eventLoop, pullRate);
|
if (lineIndex - 1 < 0) return setTimeout(eventLoop, pullRate);
|
||||||
|
@ -66,20 +70,20 @@ export async function readFromKeyboard(
|
||||||
} else {
|
} else {
|
||||||
return setTimeout(eventLoop, pullRate);
|
return setTimeout(eventLoop, pullRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!disableEcho) stream.write(character);
|
if (!disableEcho) stream.write(character);
|
||||||
} else {
|
} else {
|
||||||
lineIndex += 1;
|
lineIndex += 1;
|
||||||
|
|
||||||
// There isn't a splice method for String prototypes. So, ugh:
|
// There isn't a splice method for String prototypes. So, ugh:
|
||||||
line =
|
line =
|
||||||
line.substring(0, lineIndex - 1) +
|
line.substring(0, lineIndex - 1) +
|
||||||
character +
|
character +
|
||||||
line.substring(lineIndex - 1);
|
line.substring(lineIndex - 1);
|
||||||
|
|
||||||
if (!disableEcho) {
|
if (!disableEcho) {
|
||||||
let deltaCursor = line.length - lineIndex;
|
let deltaCursor = line.length - lineIndex;
|
||||||
|
|
||||||
// wtf?
|
// wtf?
|
||||||
if (deltaCursor < 0) {
|
if (deltaCursor < 0) {
|
||||||
console.log(
|
console.log(
|
||||||
|
@ -87,7 +91,7 @@ export async function readFromKeyboard(
|
||||||
);
|
);
|
||||||
deltaCursor = 0;
|
deltaCursor = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
stream.write(
|
stream.write(
|
||||||
line.substring(lineIndex - 1) + leftEscape.repeat(deltaCursor),
|
line.substring(lineIndex - 1) + leftEscape.repeat(deltaCursor),
|
||||||
);
|
);
|
||||||
|
@ -96,7 +100,7 @@ export async function readFromKeyboard(
|
||||||
}
|
}
|
||||||
|
|
||||||
setTimeout(eventLoop, pullRate);
|
setTimeout(eventLoop, pullRate);
|
||||||
};
|
}
|
||||||
|
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
setTimeout(eventLoop, pullRate);
|
setTimeout(eventLoop, pullRate);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue