From 0cb69076761af2ae772871ef9a399e639ddc1a40 Mon Sep 17 00:00:00 2001 From: Manoj Vivek Date: Sun, 22 Jan 2023 11:19:34 +0530 Subject: [PATCH] Handling Cli args to support passing URLs to open via cli --- desktop-app/declarations.d.ts | 1 + desktop-app/package.json | 1 + desktop-app/src/common/constants.ts | 5 ++ desktop-app/src/main/cli.ts | 36 +++++++++++ desktop-app/src/main/main.ts | 9 ++- desktop-app/src/main/util.ts | 22 +++++++ .../components/ToolBar/AddressBar/index.tsx | 60 ++++++++++++------ desktop-app/yarn.lock | 62 ++++++++++++++++++- 8 files changed, 175 insertions(+), 21 deletions(-) create mode 100644 desktop-app/src/main/cli.ts diff --git a/desktop-app/declarations.d.ts b/desktop-app/declarations.d.ts index e75c7be9..ed7a0b7e 100644 --- a/desktop-app/declarations.d.ts +++ b/desktop-app/declarations.d.ts @@ -1 +1,2 @@ declare module '*.mp3'; +declare module 'electron-args'; diff --git a/desktop-app/package.json b/desktop-app/package.json index 39bb5fb4..2a30010e 100644 --- a/desktop-app/package.json +++ b/desktop-app/package.json @@ -109,6 +109,7 @@ "bluebird": "^3.7.2", "browser-sync": "^2.27.10", "classnames": "^2.3.1", + "electron-args": "^0.1.0", "electron-debug": "^3.2.0", "electron-log": "^4.4.8", "electron-store": "^8.0.2", diff --git a/desktop-app/src/common/constants.ts b/desktop-app/src/common/constants.ts index 14b9d127..08f7b16e 100644 --- a/desktop-app/src/common/constants.ts +++ b/desktop-app/src/common/constants.ts @@ -14,6 +14,10 @@ export const PREVIEW_LAYOUTS = { export type PreviewLayout = typeof PREVIEW_LAYOUTS[keyof typeof PREVIEW_LAYOUTS]; +export interface OpenUrlArgs { + url: string; +} + export const IPC_MAIN_CHANNELS = { APP_META: 'app-meta', PERMISSION_REQUEST: 'permission-request', @@ -21,6 +25,7 @@ export const IPC_MAIN_CHANNELS = { AUTH_REQUEST: 'auth-request', AUTH_RESPONSE: 'auth-response', OPEN_EXTERNAL: 'open-external', + OPEN_URL: 'open-url', } as const; export type Channels = typeof IPC_MAIN_CHANNELS[keyof typeof IPC_MAIN_CHANNELS]; diff --git a/desktop-app/src/main/cli.ts b/desktop-app/src/main/cli.ts new file mode 100644 index 00000000..9ea09cd2 --- /dev/null +++ b/desktop-app/src/main/cli.ts @@ -0,0 +1,36 @@ +import parseArgs from 'electron-args'; + +let binaryName = 'ResponsivelyApp'; + +if (process.platform === 'darwin') { + binaryName = + '/Applications/ResponsivelyApp.app/Contents/MacOS/ResponsivelyApp'; +} + +if (process.platform === 'win32') { + binaryName = 'ResponsivelyApp.exe'; +} + +const cli = parseArgs( + ` + ResponsivelyApp + + Usage + $ ${binaryName} [path] + + Options + --help show help + --version show version + + Examples + $ ${binaryName} https://example.com + $ ${binaryName} /path/to/index.html + `, + { + alias: { + h: 'help', + }, + } +); + +export default cli; diff --git a/desktop-app/src/main/main.ts b/desktop-app/src/main/main.ts index 1f1edb9c..802b4cad 100644 --- a/desktop-app/src/main/main.ts +++ b/desktop-app/src/main/main.ts @@ -12,9 +12,10 @@ import path from 'path'; import { app, BrowserWindow, shell, ipcMain, screen } from 'electron'; import { autoUpdater } from 'electron-updater'; import log from 'electron-log'; +import cli from './cli'; import { IPC_MAIN_CHANNELS } from '../common/constants'; import MenuBuilder from './menu'; -import { resolveHtmlPath } from './util'; +import { isValidCliArgURL, resolveHtmlPath } from './util'; import { BROWSER_SYNC_HOST, initInstance } from './browser-sync'; import store from '../store'; import { initWebviewContextMenu } from './webview-context-menu/register'; @@ -102,6 +103,12 @@ const createWindow = async () => { mainWindow.on('ready-to-show', async () => { await initInstance(); + if (isValidCliArgURL(cli.input[0])) { + mainWindow?.webContents.send(IPC_MAIN_CHANNELS.OPEN_URL, { + url: cli.input[0], + }); + } + if (!mainWindow) { throw new Error('"mainWindow" is not defined'); } diff --git a/desktop-app/src/main/util.ts b/desktop-app/src/main/util.ts index 7775eda3..128ad31d 100644 --- a/desktop-app/src/main/util.ts +++ b/desktop-app/src/main/util.ts @@ -11,3 +11,25 @@ export function resolveHtmlPath(htmlFileName: string) { } return `file://${path.resolve(__dirname, '../renderer/', htmlFileName)}`; } + +export function isValidCliArgURL(arg?: string): boolean { + if (arg == null || arg === '') { + return false; + } + try { + const url = new URL(arg); + if ( + url.protocol === 'http:' || + url.protocol === 'https:' || + url.protocol === 'file:' + ) { + return true; + } + // eslint-disable-next-line no-console + console.warn('Protocol not supported', url.protocol); + } catch (e) { + // eslint-disable-next-line no-console + console.warn('Not a valid URL', arg, e); + } + return false; +} diff --git a/desktop-app/src/renderer/components/ToolBar/AddressBar/index.tsx b/desktop-app/src/renderer/components/ToolBar/AddressBar/index.tsx index 9fba222a..b87f9685 100644 --- a/desktop-app/src/renderer/components/ToolBar/AddressBar/index.tsx +++ b/desktop-app/src/renderer/components/ToolBar/AddressBar/index.tsx @@ -1,9 +1,15 @@ import { Icon } from '@iconify/react'; import cx from 'classnames'; -import { IPC_MAIN_CHANNELS } from 'common/constants'; +import { IPC_MAIN_CHANNELS, OpenUrlArgs } from 'common/constants'; import { AuthRequestArgs } from 'main/http-basic-auth'; import { PermissionRequestArg } from 'main/web-permissions/PermissionsManager'; -import { KeyboardEventHandler, useEffect, useRef, useState } from 'react'; +import { + KeyboardEventHandler, + useCallback, + useEffect, + useRef, + useState, +} from 'react'; import { useDispatch, useSelector } from 'react-redux'; import Button from 'renderer/components/Button'; import { webViewPubSub } from 'renderer/lib/pubsub'; @@ -43,6 +49,25 @@ const AddressBar = () => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [address]); + const dispatchAddress = useCallback( + (url?: string) => { + let newAddress = url ?? typedAddress; + if (newAddress.indexOf('://') === -1) { + let protocol = 'https://'; + if ( + typedAddress.indexOf('localhost') !== -1 || + typedAddress.indexOf('127.0.0.1') !== -1 + ) { + protocol = 'http://'; + } + newAddress = protocol + typedAddress; + } + + dispatch(setAddress(newAddress)); + }, + [dispatch, typedAddress] + ); + useEffect(() => { window.electron.ipcRenderer.on( IPC_MAIN_CHANNELS.PERMISSION_REQUEST, @@ -57,12 +82,25 @@ const AddressBar = () => { setAuthRequest(args); } ); + window.electron.ipcRenderer.on( + IPC_MAIN_CHANNELS.OPEN_URL, + (args) => { + dispatchAddress(args.url); + } + ); + return () => { window.electron.ipcRenderer.removeAllListeners( IPC_MAIN_CHANNELS.PERMISSION_REQUEST ); + window.electron.ipcRenderer.removeAllListeners( + IPC_MAIN_CHANNELS.AUTH_REQUEST + ); + window.electron.ipcRenderer.removeAllListeners( + IPC_MAIN_CHANNELS.OPEN_URL + ); }; - }, []); + }, [dispatchAddress]); useEffect(() => { if (homepage !== window.electron.store.get('homepage')) { @@ -81,22 +119,6 @@ const AddressBar = () => { setPermissionRequest(null); }; - const dispatchAddress = (url?: string) => { - let newAddress = url ?? typedAddress; - if (newAddress.indexOf('://') === -1) { - let protocol = 'https://'; - if ( - typedAddress.indexOf('localhost') !== -1 || - typedAddress.indexOf('127.0.0.1') !== -1 - ) { - protocol = 'http://'; - } - newAddress = protocol + typedAddress; - } - - dispatch(setAddress(newAddress)); - }; - const handleKeyDown: KeyboardEventHandler = () => { if (!isSuggesting) { setIsSuggesting(true); diff --git a/desktop-app/yarn.lock b/desktop-app/yarn.lock index 5c248134..1a0a9f6e 100644 --- a/desktop-app/yarn.lock +++ b/desktop-app/yarn.lock @@ -3868,11 +3868,24 @@ camelcase-css@^2.0.1: resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== +camelcase-keys@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-3.0.0.tgz#fc0c6c360363f7377e3793b9a16bccf1070c1ca4" + integrity sha512-U4E6A6aFyYnNW+tDt5/yIUKQURKXe3WMFPfX4FxrQFcwZ/R08AUk1xWcUtlr7oq6CV07Ji+aa69V2g7BSpblnQ== + dependencies: + camelcase "^3.0.0" + map-obj "^1.0.0" + camelcase@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39" integrity sha512-wzLkDa4K/mzI1OSITC+DUyjgIl/ETNHE9QvYgy6J6Jvqyyz4C0Xfd+lQhb19sX2jMpZV4IssUn0VDVmglV+s4g== +camelcase@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" + integrity sha512-4nhGqUkc4BqbBBB4Q6zLuD7lzzrHYrjKGeYaEji/3tFR5VdJu9v+LilhGIVe8wxEJPPOeWo7eg8dwY13TZ1BNg== + camelcase@^5.0.0, camelcase@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" @@ -4686,7 +4699,15 @@ debug@^3.2.7: dependencies: ms "^2.1.1" -decamelize@^1.0.0, decamelize@^1.2.0: +decamelize-keys@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.1.tgz#04a2d523b2f18d80d0158a43b895d56dff8d19d8" + integrity sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg== + dependencies: + decamelize "^1.1.0" + map-obj "^1.0.0" + +decamelize@^1.0.0, decamelize@^1.1.0, decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== @@ -5174,6 +5195,17 @@ ejs@^3.1.7: dependencies: jake "^10.8.5" +electron-args@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/electron-args/-/electron-args-0.1.0.tgz#90da262798f0a825aa00a2ebe943f2ba3c118f3b" + integrity sha512-CUehTgU4W8QAe+P5YWDORku1+GRMlj5XcUdzvmtmRU3scV84W/+8iJrEVOn/ARhBWlhKvS7i7T3SazLwfSlXAQ== + dependencies: + camelcase-keys "^3.0.0" + decamelize-keys "^1.1.0" + electron-is-dev "^0.1.2" + minimist "^1.2.0" + redent "^2.0.0" + electron-builder@^23.3.3: version "23.6.0" resolved "https://registry.yarnpkg.com/electron-builder/-/electron-builder-23.6.0.tgz#c79050cbdce90ed96c5feb67c34e9e0a21b5331b" @@ -5215,6 +5247,11 @@ electron-is-accelerator@^0.1.0: resolved "https://registry.yarnpkg.com/electron-is-accelerator/-/electron-is-accelerator-0.1.2.tgz#509e510c26a56b55e17f863a4b04e111846ab27b" integrity sha512-fLGSAjXZtdn1sbtZxx52+krefmtNuVwnJCV2gNiVt735/ARUboMl8jnNC9fZEqQdlAv2ZrETfmBUsoQci5evJA== +electron-is-dev@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/electron-is-dev/-/electron-is-dev-0.1.2.tgz#8a1043e32b3a1da1c3f553dce28ce764246167e3" + integrity sha512-T5WJFw6Go4UuTlnmy/4gcDW1jJKQRqGeIzff02K0jbKUwAOuQX1Kt9VWLTE4reVDlXgM5Qp6Mu7CegX/0jhnHw== + electron-is-dev@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/electron-is-dev/-/electron-is-dev-1.2.0.tgz#2e5cea0a1b3ccf1c86f577cee77363ef55deb05e" @@ -7335,6 +7372,11 @@ imurmurhash@^0.1.4: resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== +indent-string@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-3.2.0.tgz#4a5fd6d27cc332f37e5419a504dbb837105c9289" + integrity sha512-BYqTHXTGUIvg7t1r4sJNKcbDZkL92nkXA8YtRpbjFHRHGDL/NtUeiBJMeE60kIFN/Mg8ESaWQvftaYMGJzQZCQ== + indent-string@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" @@ -9061,6 +9103,11 @@ map-cache@^0.2.0, map-cache@^0.2.2: resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" integrity sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg== +map-obj@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" + integrity sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg== + map-visit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" @@ -11104,6 +11151,14 @@ rechoir@^0.7.0: dependencies: resolve "^1.9.0" +redent@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/redent/-/redent-2.0.0.tgz#c1b2007b42d57eb1389079b3c8333639d5e1ccaa" + integrity sha512-XNwrTx77JQCEMXTeb8movBKuK75MgH0RZkujNuDKCezemx/voapl9i2gCSi8WWm8+ox5ycJi1gxF22fR7c0Ciw== + dependencies: + indent-string "^3.0.0" + strip-indent "^2.0.0" + redent@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" @@ -12344,6 +12399,11 @@ strip-final-newline@^3.0.0: resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-3.0.0.tgz#52894c313fbff318835280aed60ff71ebf12b8fd" integrity sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw== +strip-indent@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-2.0.0.tgz#5ef8db295d01e6ed6cbf7aab96998d7822527b68" + integrity sha512-RsSNPLpq6YUL7QYy44RnPVTn/lcVZtb48Uof3X5JLbF4zD/Gs7ZFDv2HWol+leoQN2mT86LAzSshGfkTlSOpsA== + strip-indent@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001"