diff --git a/src/src/getAdminCredentials.ts b/src/src/getAdminCredentials.ts index 54f5a9f3..07b12d30 100755 --- a/src/src/getAdminCredentials.ts +++ b/src/src/getAdminCredentials.ts @@ -17,6 +17,7 @@ import retry from "async-retry"; import { getRpcCall } from "./api/getRpcCall"; import { API_PORT, NO_HOSTNAME_RETURNED_ERROR } from "./params"; import { renderQrCode } from "./utils/renderQrCode"; +import { withHostname } from "./utils/withHostname"; import { VpnStatus } from "./types"; /* eslint-disable no-console */ @@ -47,6 +48,8 @@ class NotReadyError extends Error { (async function(): Promise { try { + const localhost = process.argv.includes("--localhost"); + console.log( `Fetching DAppNode VPN credentials. It may take some time; use CTRL + C to stop` ); @@ -81,14 +84,15 @@ class NotReadyError extends Error { ); const { url } = await api.getMasterAdminCred(); + const outputUrl = localhost ? withHostname(url, "localhost") : url; // If rendering the QR fails, show the error and continue, the raw URL is consumable console.log(` -${await renderQrCode(url).catch(e => e.stack)} +${await renderQrCode(outputUrl).catch(e => e.stack)} To connect to your DAppNode scan the QR above or copy/paste link below into your browser: -${url}`); +${outputUrl}`); } catch (e) { // Exit process cleanly to prevent showing 'Unhandled rejection' console.error(e); diff --git a/src/src/utils/withHostname.ts b/src/src/utils/withHostname.ts new file mode 100644 index 00000000..ef25bc0d --- /dev/null +++ b/src/src/utils/withHostname.ts @@ -0,0 +1,9 @@ +export function withHostname(rawUrl: string, hostname: string): string { + try { + const parsed = new URL(rawUrl); + parsed.hostname = hostname; + return parsed.toString(); + } catch (e) { + return rawUrl; + } +} diff --git a/src/src/vpncli.ts b/src/src/vpncli.ts index 61631470..31eb3aab 100755 --- a/src/src/vpncli.ts +++ b/src/src/vpncli.ts @@ -6,6 +6,7 @@ import chalk from "chalk"; import prettyjson from "prettyjson"; import { getRpcCall } from "./api/getRpcCall"; import { API_PORT } from "./params"; +import { withHostname } from "./utils/withHostname"; /* eslint-disable no-console */ @@ -25,6 +26,13 @@ const idArg: CommandBuilder<{}, { id: string }> = yargs => demandOption: true }); +const getArg: CommandBuilder<{}, { id: string; localhost: boolean }> = yargs => + idArg(yargs).option("localhost", { + describe: "Print URL using localhost instead of the configured hostname", + type: "boolean", + default: false + }); + yargs .usage(`Usage: vpncli [options]`) .alias("h", "help") @@ -57,10 +65,13 @@ yargs .command({ command: "get ", describe: "Generate device URL to download config file.", - builder: idArg, - handler: async ({ id }) => { + builder: getArg, + handler: async ({ id, localhost }) => { const { url } = await api.getDeviceCredentials({ id }); - console.log(chalk.green(`Credentials generated for ${id}:\n${url}`)); + const outputUrl = localhost ? withHostname(url, "localhost") : url; + console.log( + chalk.green(`Credentials generated for ${id}:\n${outputUrl}`) + ); } }) .command({ diff --git a/src/test/utils/withHostname.test.ts b/src/test/utils/withHostname.test.ts new file mode 100644 index 00000000..3d09a9b4 --- /dev/null +++ b/src/test/utils/withHostname.test.ts @@ -0,0 +1,22 @@ +import "mocha"; +import { expect } from "chai"; + +import { withHostname } from "../../src/utils/withHostname"; + +describe("utils > withHostname", () => { + it("Should replace hostname preserving port, path, query, and hash", () => { + const inputUrl = + "http://9442df98a3a59a65.dyndns.dappnode.io:8092/?id=xkmTGRv3sdu9XUuz#0P%2Blna33F5loAIUx13fgm3F7%2FRLFFvOigZDt9h2kcp8%3D"; + + const outputUrl = withHostname(inputUrl, "localhost"); + + expect(outputUrl).to.equal( + "http://localhost:8092/?id=xkmTGRv3sdu9XUuz#0P%2Blna33F5loAIUx13fgm3F7%2FRLFFvOigZDt9h2kcp8%3D" + ); + }); + + it("Should return the raw input if it is not a valid URL", () => { + const inputUrl = "not-a-url"; + expect(withHostname(inputUrl, "localhost")).to.equal(inputUrl); + }); +});