From ae8fab78c84e3a333e5ae93fabcea1d468088c19 Mon Sep 17 00:00:00 2001 From: Nathan Hayfield Date: Fri, 22 Jan 2021 17:42:54 -0500 Subject: [PATCH 1/4] add support for ca files in main connection form --- package.json | 1 + src/main.dev.ts | 13 +++- src/pages/ConnectForm.tsx | 153 +++++++++++++++++++++++++------------- src/utils/binaries.ts | 19 ++++- yarn.lock | 96 +++++++++++++++++++++++- 5 files changed, 223 insertions(+), 59 deletions(-) diff --git a/package.json b/package.json index cf7cae3a..8c27c2b1 100644 --- a/package.json +++ b/package.json @@ -242,6 +242,7 @@ "electron-debug": "^3.1.0", "electron-is-packaged": "^1.0.2", "electron-log": "^4.2.4", + "electron-store": "^7.0.0", "electron-updater": "^4.3.4", "history": "^5.0.0", "menubar": "^9.0.1", diff --git a/src/main.dev.ts b/src/main.dev.ts index 0316d990..69b2da68 100644 --- a/src/main.dev.ts +++ b/src/main.dev.ts @@ -14,9 +14,10 @@ import installExtension, { REDUX_DEVTOOLS } from 'electron-devtools-installer'; import log from 'electron-log'; import { autoUpdater } from 'electron-updater'; import { menubar } from 'menubar'; +import Store from 'electron-store'; import createWindow from './utils/mainWindow'; import 'regenerator-runtime/runtime'; -import { spawnTcpConnect, TcpConnectArgs } from './utils/binaries'; +import { spawnTcpConnect, ConnectionData } from './utils/binaries'; import { CONNECTION_CLOSED, CONNECTION_RESPONSE, @@ -79,7 +80,11 @@ app.on('ready', async () => { }); mb.on('ready', async () => { - ipcMain.on('connect', (event, args: TcpConnectArgs) => { + const store = new Store(); + store.onDidChange('connections', (conns) => { + console.log(conns); + }); + ipcMain.on('connect', (event, args: ConnectionData) => { const child = spawnTcpConnect(args); child.stderr.setEncoding('utf8'); const output: string[] = []; @@ -90,10 +95,10 @@ app.on('ready', async () => { channelID: args.channelID, }); const port = `:${getPort(data.toString())}`; - const connectionInMenu = connections.some( + const connectionExists = connections.some( (connection) => connection.channelID === args.channelID ); - if (!connectionInMenu) { + if (!connectionExists) { connections.push({ url: args.destinationUrl, port, diff --git a/src/pages/ConnectForm.tsx b/src/pages/ConnectForm.tsx index 1ed26748..91b62ed6 100644 --- a/src/pages/ConnectForm.tsx +++ b/src/pages/ConnectForm.tsx @@ -19,7 +19,7 @@ import { CONNECTION_RESPONSE, DISCONNECT, } from '../utils/constants'; -import { TcpConnectArgs } from '../utils/binaries'; +import { ConnectionData } from '../utils/binaries'; const useStyles = makeStyles(() => ({ container: { @@ -43,35 +43,82 @@ interface Props { onComplete?: () => void; } +const noErrors = { + localAddress: false, + destinationUrl: false, + pomeriumUrl: false, + caFilePath: false, + caFileText: false, +}; + +const initialConnectionData: ConnectionData = { + destinationUrl: '', + localAddress: '', + pomeriumUrl: '', + disableTLS: false, + caFileText: '', + caFilePath: '', + channelID: '', +}; + const ConnectForm: FC = () => { const classes = useStyles(); - const [disableTLS, setDisableTLS] = useState(false); - const [localAddress, setLocalAddress] = useState(''); - const [localError, setLocalError] = useState(false); - const [destinationUrl, setDestinationUrl] = useState(''); - const [destinationError, setDestinationError] = useState(true); - const [pomeriumUrl, setPomeriumUrl] = useState(''); - const [pomeriumUrlError, setPomeriumUrlError] = useState(false); const [connected, setConnected] = useState(false); const [output, setOutput] = useState([]); - const [channelID, setChannelID] = useState(''); // keeps communication per connection + const [errors, setErrors] = useState(noErrors); + const [connectionData, setConnectionData] = useState(initialConnectionData); const handleSubmit = (evt: React.FormEvent): void => { evt.preventDefault(); }; const saveDestination = (value: string): void => { - setDestinationUrl(value); - setDestinationError(!isUrl(value) || !value.trim()); + setConnectionData({ + ...connectionData, + ...{ destinationUrl: value.trim() }, + }); + setErrors({ + ...errors, + ...{ destinationUrl: !isUrl(value) || !value.trim() }, + }); }; const saveLocal = (value: string): void => { - setLocalAddress(value); - setLocalError(!isIp(value)); + setConnectionData({ ...connectionData, ...{ localAddress: value.trim() } }); + setErrors({ + ...errors, + ...{ localAddress: !isIp(value) }, + }); }; const savePomeriumUrl = (value: string): void => { - setPomeriumUrl(value); - setPomeriumUrlError(!isUrl(value)); + setConnectionData({ ...connectionData, ...{ pomeriumUrl: value.trim() } }); + setErrors({ + ...errors, + ...{ pomeriumUrl: !isUrl(value) }, + }); + }; + + const saveDisableTLS = (): void => { + setConnectionData({ + ...connectionData, + ...{ disableTLS: !connectionData.disableTLS }, + }); + }; + + const saveCaFilePath = (value: string): void => { + setConnectionData({ ...connectionData, ...{ caFilePath: value.trim() } }); + setErrors({ + ...errors, + ...{ caFilePath: !!connectionData.caFileText }, + }); + }; + + const saveCaFileText = (value: string): void => { + setConnectionData({ ...connectionData, ...{ caFileText: value.trim() } }); + setErrors({ + ...errors, + ...{ caFileText: !!connectionData.caFilePath }, + }); }; const disconnect = (channel_id: string): void => { @@ -80,47 +127,36 @@ const ConnectForm: FC = () => { useEffect(() => { ipcRenderer.on(CONNECTION_RESPONSE, (_, msg) => { - if (msg.channelID === channelID) { + if (msg.channelID === connectionData.channelID) { setOutput(msg.output); } }); ipcRenderer.on(CONNECTION_CLOSED, (_, msg) => { - if (msg.channelID === channelID) { + if (msg.channelID === connectionData.channelID) { setConnected(false); } }); - }, [channelID]); + }, [connectionData.channelID]); const connect = (): void => { - if (!localError && !destinationError && !pomeriumUrlError) { + if (Object.values(errors).every((error) => !error)) { const uuid = uuidv4(); - setChannelID(uuid); ipcRenderer.removeAllListeners(CONNECTION_RESPONSE); ipcRenderer.removeAllListeners(CONNECTION_CLOSED); setOutput([]); setConnected(true); - const args: TcpConnectArgs = { - destinationUrl, - localAddress, - pomeriumUrl, - disableTLS, - channelID: uuid, - }; - + const args: ConnectionData = { ...connectionData }; + args.channelID = uuid; + setConnectionData(args); ipcRenderer.send('connect', args); } }; const clear = (): void => { - setDisableTLS(false); - setLocalAddress(''); - setLocalError(false); - setDestinationUrl(''); - setDestinationError(true); - setPomeriumUrl(''); - setPomeriumUrlError(false); + setConnectionData(initialConnectionData); setConnected(false); setOutput([]); + setErrors(noErrors); }; return ( @@ -135,9 +171,9 @@ const ConnectForm: FC = () => { saveDestination(evt.target.value)} variant="outlined" autoFocus @@ -149,10 +185,10 @@ const ConnectForm: FC = () => { label="Disable TLS Verification" control={ setDisableTLS(!disableTLS)} + onChange={saveDisableTLS} /> } /> @@ -162,8 +198,8 @@ const ConnectForm: FC = () => { saveLocal(evt.target.value)} variant="outlined" /> @@ -172,12 +208,34 @@ const ConnectForm: FC = () => { savePomeriumUrl(evt.target.value)} variant="outlined" /> + + saveCaFilePath(evt.target.value)} + variant="outlined" + /> + + + saveCaFileText(evt.target.value)} + variant="outlined" + multiline + rows={4} + /> + = () => { fullWidth type="button" variant="outlined" - onClick={() => disconnect(channelID)} + onClick={() => disconnect(connectionData.channelID)} disabled={!connected} color="primary" className={classes.button} @@ -203,12 +261,7 @@ const ConnectForm: FC = () => { fullWidth type="button" variant="outlined" - disabled={ - localError || - destinationError || - pomeriumUrlError || - connected - } + disabled={Object.values(errors).some(Boolean) || connected} color="primary" className={classes.button} onClick={connect} diff --git a/src/utils/binaries.ts b/src/utils/binaries.ts index 77407c20..369194dd 100644 --- a/src/utils/binaries.ts +++ b/src/utils/binaries.ts @@ -17,15 +17,17 @@ export const pomeriumCli: string = getAssetPath( 'pomerium-cli' ); -export interface TcpConnectArgs { +export interface ConnectionData { destinationUrl: string; channelID: string; localAddress?: string; pomeriumUrl?: string; disableTLS?: boolean; + caFilePath?: string; + caFileText?: string; } -const buildSpawnArgs = (args: TcpConnectArgs) => { +const buildSpawnArgs = (args: ConnectionData) => { const spawnArgs = ['tcp', args.destinationUrl]; if (args.localAddress) { spawnArgs.push(`--listen`); @@ -38,11 +40,22 @@ const buildSpawnArgs = (args: TcpConnectArgs) => { if (args.disableTLS) { spawnArgs.push('--disable-tls-verification'); } + + if (args.caFilePath) { + spawnArgs.push(`--alternate-ca-path`); + spawnArgs.push(args.caFilePath); + } + + if (args.caFileText) { + spawnArgs.push(`--ca-cert`); + spawnArgs.push(btoa(args.caFileText)); + } + return spawnArgs; }; export const spawnTcpConnect = ( - args: TcpConnectArgs + args: ConnectionData ): child_process.ChildProcessWithoutNullStreams => { return child_process.spawn(pomeriumCli, buildSpawnArgs(args)); }; diff --git a/yarn.lock b/yarn.lock index 9d8995f1..907c39ff 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2227,6 +2227,16 @@ ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.0, ajv@^6.12.3, ajv@^6.12.4, ajv json-schema-traverse "^0.4.1" uri-js "^4.2.2" +ajv@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-7.0.3.tgz#13ae747eff125cafb230ac504b2406cf371eece2" + integrity sha512-R50QRlXSxqXcQP5SvKUrw8VZeypvo12i2IX0EeR5PiZ7bEKeHWgzgo264LDadUsCU42lTJVhFikTqJwNeH34gQ== + dependencies: + fast-deep-equal "^3.1.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.2.2" + alphanum-sort@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" @@ -2587,6 +2597,11 @@ atob@^2.1.2: resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== +atomically@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/atomically/-/atomically-1.7.0.tgz#c07a0458432ea6dbc9a3506fffa424b48bccaafe" + integrity sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w== + aws-sign2@~0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" @@ -3542,6 +3557,22 @@ concurrently@^5.3.0: tree-kill "^1.2.2" yargs "^13.3.0" +conf@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/conf/-/conf-9.0.0.tgz#86fd1a8e58da81a8fae2a8a9feeb3a45ef68d303" + integrity sha512-TGrWTD/tviifvdeoOZIYBmlceYJ0wq2lCzqbv7bT4KgostaCyyYe9uYWZdzPdhl4mROQXwMcc/iuAUsMTzf7pQ== + dependencies: + ajv "^7.0.3" + atomically "^1.7.0" + debounce-fn "^4.0.0" + dot-prop "^6.0.1" + env-paths "^2.2.0" + json-schema-typed "^7.0.3" + make-dir "^3.1.0" + onetime "^5.1.2" + pkg-up "^3.1.0" + semver "^7.3.4" + config-chain@^1.1.11: version "1.1.12" resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.12.tgz#0fde8d091200eb5e808caf25fe618c02f48e4efa" @@ -3994,6 +4025,13 @@ date-fns@^2.0.1: resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.16.1.tgz#05775792c3f3331da812af253e1a935851d3834b" integrity sha512-sAJVKx/FqrLYHAQeN7VpJrPhagZc9R4ImZIWYRFZaaohR3KzmuK88touwsSwSVT8Qcbd4zoDsnGfX4GFB4imyQ== +debounce-fn@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/debounce-fn/-/debounce-fn-4.0.0.tgz#ed76d206d8a50e60de0dd66d494d82835ffe61c7" + integrity sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ== + dependencies: + mimic-fn "^3.0.0" + debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -4355,6 +4393,13 @@ dot-prop@^5.2.0: dependencies: is-obj "^2.0.0" +dot-prop@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-6.0.1.tgz#fc26b3cf142b9e59b74dbd39ed66ce620c681083" + integrity sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA== + dependencies: + is-obj "^2.0.0" + dotenv-expand@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0" @@ -4507,6 +4552,14 @@ electron-rebuild@^2.3.2: tar "^6.0.5" yargs "^16.0.0" +electron-store@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/electron-store/-/electron-store-7.0.0.tgz#34fee526dc4ca03933e6321667e63d9d27e874e1" + integrity sha512-ktoziul0TNETBxjZ1QLCPT9OGIQfDNb1AgvY29sqzca9c6H/ccV2NV+2d9epdlfo6dNZYI16BlHDVtFp9t3rvw== + dependencies: + conf "^9.0.0" + type-fest "^0.20.2" + electron-to-chromium@^1.3.591: version "1.3.600" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.600.tgz#eb6aa7233ca1fbf0fa9b5943c0f1061b54a433bf" @@ -7313,6 +7366,16 @@ json-schema-traverse@^0.4.1: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + +json-schema-typed@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/json-schema-typed/-/json-schema-typed-7.0.3.tgz#23ff481b8b4eebcd2ca123b4fa0409e66469a2d9" + integrity sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A== + json-schema@0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" @@ -8013,6 +8076,11 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== +mimic-fn@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-3.1.0.tgz#65755145bbf3e36954b949c16450427451d5ca74" + integrity sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ== + mimic-response@^1.0.0, mimic-response@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" @@ -8595,7 +8663,7 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" -onetime@^5.1.0: +onetime@^5.1.0, onetime@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== @@ -9032,6 +9100,13 @@ pkg-dir@^4.1.0, pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" +pkg-up@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-3.1.0.tgz#100ec235cc150e4fd42519412596a28512a0def5" + integrity sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA== + dependencies: + find-up "^3.0.0" + please-upgrade-node@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942" @@ -10007,6 +10082,11 @@ require-directory@^2.1.1: resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + require-main-filename@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" @@ -10349,7 +10429,7 @@ semver@7.0.0: resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== -semver@7.3.2, semver@^7.2.1, semver@^7.3.2: +semver@7.3.2: version "7.3.2" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== @@ -10359,6 +10439,13 @@ semver@^6.0.0, semver@^6.2.0, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== +semver@^7.2.1, semver@^7.3.2, semver@^7.3.4: + version "7.3.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97" + integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw== + dependencies: + lru-cache "^6.0.0" + send@0.17.1: version "0.17.1" resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" @@ -11412,6 +11499,11 @@ type-fest@^0.13.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.13.1.tgz#0172cb5bce80b0bd542ea348db50c7e21834d934" integrity sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg== +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + type-fest@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" From fc4003393ef8f92e25cb638cea0ff7abe8739c29 Mon Sep 17 00:00:00 2001 From: nathan Date: Tue, 23 Feb 2021 16:14:49 -0500 Subject: [PATCH 2/4] connect, disconnect, delete from menu --- src/utils/trayMenu.ts | 93 ------------------------------- src/utils/trayMenuHelper.ts | 106 ++++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 93 deletions(-) delete mode 100644 src/utils/trayMenu.ts create mode 100644 src/utils/trayMenuHelper.ts diff --git a/src/utils/trayMenu.ts b/src/utils/trayMenu.ts deleted file mode 100644 index 15f75915..00000000 --- a/src/utils/trayMenu.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { - app, - BrowserWindow, - Menu, - MenuItem, - MenuItemConstructorOptions, - shell, - Tray, -} from 'electron'; -import child_process from 'child_process'; -import { getAssetPath } from './binaries'; - -export interface Connection { - url: string; - port: string; - child: child_process.ChildProcessWithoutNullStreams; - channelID: string; -} - -const buildConnections = (connections: Connection[]) => { - return connections.map((connection) => { - return { - label: `${connection.url} -> ${connection.port}`, - submenu: [ - { - label: 'Disconnect', - click: () => { - connection.child.kill(); - }, - }, - ], - }; - }); -}; - -const buildMenuTemplate = ( - mainWindow: BrowserWindow | null, - connections: Connection[] -) => { - const template: (MenuItemConstructorOptions | MenuItem)[] = [ - { - label: 'Connect', - click() { - mainWindow?.webContents.send('redirectTo', '/connect'); - mainWindow?.show(); - }, - }, - { - label: 'Settings', - click() { - mainWindow?.webContents.send('redirectTo', '/hello2'); - mainWindow?.show(); - }, - }, - ]; - if (connections.length) { - template.push({ - label: 'Connections', - submenu: buildConnections(connections), - }); - } - - template.push({ - label: 'Help', - click() { - shell.openExternal( - 'https://github.com/pomerium/pomerium-tcp-connector#readme' - ); - }, - }); - - template.push({ - label: 'Quit', - click() { - app.quit(); - }, - }); - - return template; -}; - -export const createContextMenu = ( - mainWindow: BrowserWindow | null, - connections: Connection[] -): Menu => { - return Menu.buildFromTemplate(buildMenuTemplate(mainWindow, connections)); -}; - -export const createTray = (mainWindow: BrowserWindow): Tray => { - const tray = new Tray(getAssetPath('icons', '24x24.png')); - tray.setContextMenu(createContextMenu(mainWindow, [])); - return tray; -}; diff --git a/src/utils/trayMenuHelper.ts b/src/utils/trayMenuHelper.ts new file mode 100644 index 00000000..bbcf368a --- /dev/null +++ b/src/utils/trayMenuHelper.ts @@ -0,0 +1,106 @@ +import { + app, + BrowserWindow, + Menu, + MenuItem, + MenuItemConstructorOptions, + shell, + Tray, +} from 'electron'; +import { getAssetPath } from './binaries'; +import Connections from './connections'; +import { MENU_CHANGE } from './constants'; + +const buildConnections = ( + connections: Connections, + mainWindow: BrowserWindow | null +) => { + return Object.values(connections.getMenuConnections()).map((connection) => { + const connectionOptions = []; + if (connection.child) { + connectionOptions.push({ + label: 'Disconnect', + click: () => { + connections.disconnect(connection.channelID); + mainWindow?.webContents.send(MENU_CHANGE, {}); + }, + }); + } else { + connectionOptions.push({ + label: 'Connect', + click: () => { + connections.connect(connection.channelID, null); + console.log(mainWindow); + mainWindow?.webContents.send(MENU_CHANGE, {}); + }, + }); + } + return { + label: `${connection.url} -> `, + sublabel: connection.port, + submenu: connectionOptions, + }; + }); +}; + +const buildMenuTemplate = ( + mainWindow: BrowserWindow | null, + connections: Connections +) => { + const template: (MenuItemConstructorOptions | MenuItem)[] = [ + { + label: 'Connect', + click() { + mainWindow?.webContents.send('redirectTo', '/connect'); + mainWindow?.show(); + }, + }, + { + label: 'Settings', + click() { + mainWindow?.webContents.send('redirectTo', '/hello2'); + mainWindow?.show(); + }, + }, + ]; + if (Object.values(connections.getMenuConnections()).length) { + template.push({ + label: 'Connections', + submenu: buildConnections(connections, mainWindow), + }); + } + + template.push({ + label: 'Help', + click() { + shell.openExternal( + 'https://github.com/pomerium/pomerium-tcp-connector#readme' + ); + }, + }); + + template.push({ + label: 'Quit', + click() { + app.quit(); + }, + }); + + return template; +}; + +export const createContextMenu = ( + mainWindow: BrowserWindow | null, + connections: Connections +): Menu => { + return Menu.buildFromTemplate(buildMenuTemplate(mainWindow, connections)); +}; + +export const createTray = ( + mainWindow: BrowserWindow, + connections: Connections +): Tray => { + const tray = new Tray(getAssetPath('icons', '24x24.png')); + tray.setContextMenu(createContextMenu(mainWindow, connections)); + return tray; +}; From 44ac6c0d81554ff1a425f4210922d4abe7ab9cba Mon Sep 17 00:00:00 2001 From: nathan Date: Tue, 23 Feb 2021 16:20:01 -0500 Subject: [PATCH 3/4] push actual changes --- src/main.dev.ts | 89 +++++----------- src/pages/ConnectForm.tsx | 2 +- src/utils/binaries.ts | 11 +- src/utils/connections.ts | 100 ++++++++++++++++++ src/utils/constants.ts | 21 ++++ src/utils/trayMenuHelper.ts | 195 +++++++++++++++++++++--------------- 6 files changed, 264 insertions(+), 154 deletions(-) create mode 100644 src/utils/connections.ts diff --git a/src/main.dev.ts b/src/main.dev.ts index 69b2da68..8258889f 100644 --- a/src/main.dev.ts +++ b/src/main.dev.ts @@ -14,21 +14,19 @@ import installExtension, { REDUX_DEVTOOLS } from 'electron-devtools-installer'; import log from 'electron-log'; import { autoUpdater } from 'electron-updater'; import { menubar } from 'menubar'; -import Store from 'electron-store'; import createWindow from './utils/mainWindow'; import 'regenerator-runtime/runtime'; -import { spawnTcpConnect, ConnectionData } from './utils/binaries'; import { - CONNECTION_CLOSED, - CONNECTION_RESPONSE, DISCONNECT, isDev, isProd, prodDebug, + ConnectionData, + CONNECT, } from './utils/constants'; -import { createTray, createContextMenu, Connection } from './utils/trayMenu'; +import Connections from './utils/connections'; +import TrayMenuHelper from './utils/trayMenuHelper'; -let connections: Connection[] = []; let mainWindow: BrowserWindow | null; class AppUpdater { @@ -39,11 +37,6 @@ class AppUpdater { } } -const getPort = (text: string) => { - const parts = text.split(':'); - return parseInt(parts[parts.length - 1], 10); -}; - if (isProd) { const sourceMapSupport = require('source-map-support'); sourceMapSupport.install(); @@ -59,12 +52,6 @@ app.on('activate', async () => { if (mainWindow === null) mainWindow = createWindow(); }); -app.on('before-quit', () => { - connections.forEach((connection) => connection.child.kill()); - mainWindow?.removeAllListeners('close'); - mainWindow?.close(); -}); - app.on('ready', async () => { // Remove this if your app does not use auto updates // eslint-disable-next-line @@ -74,56 +61,34 @@ app.on('ready', async () => { .catch((err: Error) => console.log('An error occurred: ', err)); mainWindow = createWindow(); mainWindow?.loadURL(`file://${__dirname}/index.html`); - const tray = createTray(mainWindow); - const mb = menubar({ + const connections = new Connections(); + const trayMenuHelper = new TrayMenuHelper(connections, mainWindow, null); + const tray = trayMenuHelper.createTray(); + const options = { height: 2000, width: 2 }; + const menu = menubar({ + browserWindow: options, tray, }); + trayMenuHelper.setMenu(menu); - mb.on('ready', async () => { - const store = new Store(); - store.onDidChange('connections', (conns) => { - console.log(conns); + menu.on('ready', async () => { + menu.tray.setContextMenu(trayMenuHelper.createContextMenu(connections)); + ipcMain.on(CONNECT, (evt, args: ConnectionData) => { + connections.saveConnection(args); + connections.createMenuConnectionFromData(args); + connections.connect(args.channelID, evt); + menu.tray.setContextMenu(trayMenuHelper.createContextMenu(connections)); }); - ipcMain.on('connect', (event, args: ConnectionData) => { - const child = spawnTcpConnect(args); - child.stderr.setEncoding('utf8'); - const output: string[] = []; - child.stderr.on('data', (data) => { - output.push(data.toString()); - event.sender.send(CONNECTION_RESPONSE, { - output, - channelID: args.channelID, - }); - const port = `:${getPort(data.toString())}`; - const connectionExists = connections.some( - (connection) => connection.channelID === args.channelID - ); - if (!connectionExists) { - connections.push({ - url: args.destinationUrl, - port, - child, - channelID: args.channelID, - }); - mb.tray.setContextMenu(createContextMenu(mainWindow, connections)); - } - }); - ipcMain.on(DISCONNECT, (_, msg) => { - if (msg.channelID === args.channelID) { - child.kill(); - } - }); - child.on('exit', (code) => { - event.sender.send(CONNECTION_CLOSED, { - code, - channelID: args.channelID, - }); - child.removeAllListeners(); - connections = connections.filter((connection) => { - return connection.channelID !== args.channelID; - }); - mb.tray.setContextMenu(createContextMenu(mainWindow, connections)); + ipcMain.on(DISCONNECT, (_evt, msg) => { + connections.disconnect(msg.channelID); + menu.tray.setContextMenu(trayMenuHelper.createContextMenu(connections)); + }); + app.on('before-quit', () => { + Object.values(connections.getMenuConnections()).forEach((conn) => { + connections.disconnect(conn.channelID); }); + mainWindow?.removeAllListeners('close'); + mainWindow?.close(); }); }); }); diff --git a/src/pages/ConnectForm.tsx b/src/pages/ConnectForm.tsx index 91b62ed6..18bb556c 100644 --- a/src/pages/ConnectForm.tsx +++ b/src/pages/ConnectForm.tsx @@ -18,8 +18,8 @@ import { CONNECTION_CLOSED, CONNECTION_RESPONSE, DISCONNECT, + ConnectionData, } from '../utils/constants'; -import { ConnectionData } from '../utils/binaries'; const useStyles = makeStyles(() => ({ container: { diff --git a/src/utils/binaries.ts b/src/utils/binaries.ts index 369194dd..7608611d 100644 --- a/src/utils/binaries.ts +++ b/src/utils/binaries.ts @@ -2,6 +2,7 @@ import { app } from 'electron'; import path from 'path'; import * as child_process from 'child_process'; import getPlatform from '../platform'; +import { ConnectionData } from './constants'; export const RESOURCES_PATH = app.isPackaged ? path.join(process.resourcesPath, 'assets') @@ -17,16 +18,6 @@ export const pomeriumCli: string = getAssetPath( 'pomerium-cli' ); -export interface ConnectionData { - destinationUrl: string; - channelID: string; - localAddress?: string; - pomeriumUrl?: string; - disableTLS?: boolean; - caFilePath?: string; - caFileText?: string; -} - const buildSpawnArgs = (args: ConnectionData) => { const spawnArgs = ['tcp', args.destinationUrl]; if (args.localAddress) { diff --git a/src/utils/connections.ts b/src/utils/connections.ts new file mode 100644 index 00000000..83a7cbf9 --- /dev/null +++ b/src/utils/connections.ts @@ -0,0 +1,100 @@ +import Store from 'electron-store'; +import { IpcMainEvent } from 'electron'; +import { spawnTcpConnect } from './binaries'; +import { + CONNECTION_CLOSED, + CONNECTION_RESPONSE, + ConnectionData, + MenuConnection, +} from './constants'; + +export default class Connections { + connectionsData: Record = {}; + + menuConnections: Record = {}; + + store: Store; + + constructor() { + this.store = new Store({ name: 'connections' }); + const data = this.store.get('connections') as Record< + ConnectionData['channelID'], + ConnectionData + >; + if (data) { + this.connectionsData = data; + } + Object.values(this.connectionsData).forEach((conn) => { + this.createMenuConnectionFromData(conn); + }); + } + + getPort = (text: string) => { + const parts = text.split(':'); + return parseInt(parts[parts.length - 1], 10); + }; + + saveConnection(conn: ConnectionData) { + this.connectionsData[conn.channelID] = conn; + this.store.set('connections', this.connectionsData); + } + + getConnection(channelID: ConnectionData['channelID']) { + return this.connectionsData[channelID]; + } + + deleteConnection(channelID: ConnectionData['channelID']) { + this.disconnect(channelID); + delete this.connectionsData[channelID]; + delete this.menuConnections[channelID]; + this.store.set('connections', this.connectionsData); + } + + getConnections() { + return this.connectionsData; + } + + connect(channelID: MenuConnection['channelID'], evt: IpcMainEvent | null) { + if (this.menuConnections[channelID].channelID) { + const child = spawnTcpConnect(this.connectionsData[channelID]); + child.stderr.setEncoding('utf8'); + this.menuConnections[channelID].child = child; + child.stderr.on('data', (data) => { + this.menuConnections[channelID].output.push(data.toString()); + evt?.sender.send(CONNECTION_RESPONSE, { + output: this.menuConnections[channelID].output, + channelID, + }); + this.menuConnections[channelID].port = `:${this.getPort( + data.toString() + )}`; + }); + child.on('exit', (code) => { + evt?.sender.send(CONNECTION_CLOSED, { + code, + channelID, + }); + child.removeAllListeners(); + }); + } + } + + disconnect(channelID: MenuConnection['channelID']) { + this.menuConnections[channelID].child?.kill(); + this.menuConnections[channelID].child = null; + } + + createMenuConnectionFromData(conn: ConnectionData) { + this.menuConnections[conn.channelID] = { + channelID: conn.channelID, + child: null, + output: [], + port: conn.localAddress || '', + url: conn.destinationUrl, + }; + } + + getMenuConnections() { + return this.menuConnections; + } +} diff --git a/src/utils/constants.ts b/src/utils/constants.ts index f45a35cd..6380efe9 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -1,3 +1,5 @@ +import child_process from 'child_process'; + export const isProd = process.env.NODE_ENV === 'production'; export const isDev = process.env.NODE_ENV === 'development'; export const prodDebug = process.env.DEBUG_PROD === 'true'; @@ -5,3 +7,22 @@ export const prodDebug = process.env.DEBUG_PROD === 'true'; export const CONNECTION_RESPONSE = 'connection-response'; export const CONNECTION_CLOSED = 'connection-close'; export const DISCONNECT = 'disconnect'; +export const CONNECT = 'connect'; + +export interface ConnectionData { + destinationUrl: string; + channelID: string; + localAddress?: string; + pomeriumUrl?: string; + disableTLS?: boolean; + caFilePath?: string; + caFileText?: string; +} + +export interface MenuConnection { + url: string; + port: string; + child: child_process.ChildProcessWithoutNullStreams | null; + channelID: string; + output: string[]; +} diff --git a/src/utils/trayMenuHelper.ts b/src/utils/trayMenuHelper.ts index bbcf368a..b7608200 100644 --- a/src/utils/trayMenuHelper.ts +++ b/src/utils/trayMenuHelper.ts @@ -7,100 +7,133 @@ import { shell, Tray, } from 'electron'; +import { Menubar } from 'menubar'; import { getAssetPath } from './binaries'; import Connections from './connections'; -import { MENU_CHANGE } from './constants'; -const buildConnections = ( - connections: Connections, - mainWindow: BrowserWindow | null -) => { - return Object.values(connections.getMenuConnections()).map((connection) => { - const connectionOptions = []; - if (connection.child) { - connectionOptions.push({ - label: 'Disconnect', - click: () => { - connections.disconnect(connection.channelID); - mainWindow?.webContents.send(MENU_CHANGE, {}); - }, - }); - } else { - connectionOptions.push({ +export default class TrayMenuHelper { + connections: Connections; + + mainWindow: BrowserWindow | null; + + menu: Menubar | null; + + constructor( + connections: Connections, + mainWindow: BrowserWindow | null, + menu: Menubar | null + ) { + this.connections = connections; + this.mainWindow = mainWindow; + this.menu = menu; + } + + setConnections = (connections: Connections) => { + this.connections = connections; + }; + + setMenu = (menu: Menubar) => { + this.menu = menu; + }; + + setMainWindow = (mainWindow: BrowserWindow) => { + this.mainWindow = mainWindow; + }; + + buildConnections = () => { + return Object.values(this.connections.getMenuConnections()).map( + (connection) => { + const connectionOptions = []; + if (connection.child) { + connectionOptions.push({ + label: 'Disconnect', + click: () => { + this.connections.disconnect(connection.channelID); + this.menu?.tray.setContextMenu( + this.createContextMenu(this.connections) + ); + }, + }); + } else { + connectionOptions.push({ + label: 'Connect', + click: () => { + this.connections.connect(connection.channelID, null); + this.menu?.tray.setContextMenu( + this.createContextMenu(this.connections) + ); + }, + }); + } + connectionOptions.push({ + label: 'Delete', + click: () => { + this.connections.deleteConnection(connection.channelID); + this.menu?.tray.setContextMenu( + this.createContextMenu(this.connections) + ); + }, + }); + return { + label: `${connection.url} -> ${connection.port}`, + submenu: connectionOptions, + }; + } + ); + }; + + buildMenuTemplate = () => { + const { mainWindow, connections, buildConnections } = this; + const template: (MenuItemConstructorOptions | MenuItem)[] = [ + { label: 'Connect', - click: () => { - connections.connect(connection.channelID, null); - console.log(mainWindow); - mainWindow?.webContents.send(MENU_CHANGE, {}); + click() { + mainWindow?.webContents.send('redirectTo', '/connect'); + mainWindow?.show(); }, + }, + { + label: 'Settings', + click() { + mainWindow?.webContents.send('redirectTo', '/hello2'); + mainWindow?.show(); + }, + }, + ]; + if (Object.values(connections.getMenuConnections()).length) { + template.push({ + label: 'Connections', + submenu: buildConnections(), }); } - return { - label: `${connection.url} -> `, - sublabel: connection.port, - submenu: connectionOptions, - }; - }); -}; -const buildMenuTemplate = ( - mainWindow: BrowserWindow | null, - connections: Connections -) => { - const template: (MenuItemConstructorOptions | MenuItem)[] = [ - { - label: 'Connect', + template.push({ + label: 'Help', click() { - mainWindow?.webContents.send('redirectTo', '/connect'); - mainWindow?.show(); + shell.openExternal( + 'https://github.com/pomerium/pomerium-tcp-connector#readme' + ); }, - }, - { - label: 'Settings', + }); + + template.push({ + label: 'Quit', click() { - mainWindow?.webContents.send('redirectTo', '/hello2'); - mainWindow?.show(); + app.quit(); }, - }, - ]; - if (Object.values(connections.getMenuConnections()).length) { - template.push({ - label: 'Connections', - submenu: buildConnections(connections, mainWindow), }); - } - - template.push({ - label: 'Help', - click() { - shell.openExternal( - 'https://github.com/pomerium/pomerium-tcp-connector#readme' - ); - }, - }); - - template.push({ - label: 'Quit', - click() { - app.quit(); - }, - }); - return template; -}; + return template; + }; -export const createContextMenu = ( - mainWindow: BrowserWindow | null, - connections: Connections -): Menu => { - return Menu.buildFromTemplate(buildMenuTemplate(mainWindow, connections)); -}; + createContextMenu = (connections: Connections): Menu => { + this.setConnections(connections); + return Menu.buildFromTemplate(this.buildMenuTemplate()); + }; -export const createTray = ( - mainWindow: BrowserWindow, - connections: Connections -): Tray => { - const tray = new Tray(getAssetPath('icons', '24x24.png')); - tray.setContextMenu(createContextMenu(mainWindow, connections)); - return tray; -}; + createTray = (): Tray => { + const tray = new Tray(getAssetPath('icons', '24x24.png')); + tray.setContextMenu(this.createContextMenu(this.connections)); + return tray; + }; +} From 8ff2e52a23b9e8a67247cafe29c448ad7d431483 Mon Sep 17 00:00:00 2001 From: nathan Date: Tue, 23 Feb 2021 16:28:27 -0500 Subject: [PATCH 4/4] remove unused options --- src/main.dev.ts | 2 -- src/main.prod.js.LICENSE.txt | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.dev.ts b/src/main.dev.ts index 8258889f..e05dc18e 100644 --- a/src/main.dev.ts +++ b/src/main.dev.ts @@ -64,9 +64,7 @@ app.on('ready', async () => { const connections = new Connections(); const trayMenuHelper = new TrayMenuHelper(connections, mainWindow, null); const tray = trayMenuHelper.createTray(); - const options = { height: 2000, width: 2 }; const menu = menubar({ - browserWindow: options, tray, }); trayMenuHelper.setMenu(menu); diff --git a/src/main.prod.js.LICENSE.txt b/src/main.prod.js.LICENSE.txt index 15036cdc..577d76ef 100644 --- a/src/main.prod.js.LICENSE.txt +++ b/src/main.prod.js.LICENSE.txt @@ -1 +1,3 @@ /*! http://mths.be/fromcodepoint v0.1.0 by @mathias */ + +/** @license URI.js v4.4.0 (c) 2011 Gary Court. License: http://github.com/garycourt/uri-js */