Merge origin/main

This commit is contained in:
Manoj Vivek 2022-12-02 13:15:00 +05:30
commit c1e29ed8cd
10 changed files with 251 additions and 112 deletions

View file

@ -13,3 +13,13 @@ export const PREVIEW_LAYOUTS = {
export type PreviewLayout =
typeof PREVIEW_LAYOUTS[keyof typeof PREVIEW_LAYOUTS];
export const IPC_MAIN_CHANNELS = {
APP_META: 'app-meta',
PERMISSION_REQUEST: 'permission-request',
PERMISSION_RESPONSE: 'permission-response',
AUTH_REQUEST: 'auth-request',
AUTH_RESPONSE: 'auth-response',
} as const;
export type Channels = typeof IPC_MAIN_CHANNELS[keyof typeof IPC_MAIN_CHANNELS];

View file

@ -0,0 +1,44 @@
import { AuthInfo, app, BrowserWindow, ipcMain } from 'electron';
import { IPC_MAIN_CHANNELS } from '../../common/constants';
export type AuthRequestArgs = AuthInfo;
export interface AuthResponseArgs {
username: string;
password: string;
authInfo: AuthInfo;
}
type Callback = (username: string, password: string) => void;
const inProgressAuthentications: { [key: string]: Callback[] } = {};
const handleLogin = async (
authInfo: AuthInfo,
mainWindow: BrowserWindow,
callback: (username: string, password: string) => void
) => {
if (inProgressAuthentications[authInfo.host]) {
inProgressAuthentications[authInfo.host].push(callback);
return;
}
inProgressAuthentications[authInfo.host] = [callback];
mainWindow.webContents.send(IPC_MAIN_CHANNELS.AUTH_REQUEST, authInfo);
ipcMain.once(
IPC_MAIN_CHANNELS.AUTH_RESPONSE,
(_, { authInfo: respAuthInfo, username, password }: AuthResponseArgs) => {
inProgressAuthentications[respAuthInfo.host].forEach((cb) =>
cb(username, password)
);
delete inProgressAuthentications[respAuthInfo.host];
}
);
};
export const initHttpBasicAuthHandlers = (mainWindow: BrowserWindow) => {
app.on('login', (event, _webContents, _request, authInfo, callback) => {
event.preventDefault();
handleLogin(authInfo, mainWindow, callback);
});
};

View file

@ -12,6 +12,7 @@ import path from 'path';
import { app, BrowserWindow, shell, ipcMain, screen } from 'electron';
import { autoUpdater } from 'electron-updater';
import log from 'electron-log';
import { IPC_MAIN_CHANNELS } from 'common/constants';
import MenuBuilder from './menu';
import { resolveHtmlPath } from './util';
import { BROWSER_SYNC_HOST, initInstance } from './browser-sync';
@ -22,6 +23,7 @@ import { initDevtoolsHandlers } from './devtools';
import { initWebviewStorageManagerHandlers } from './webview-storage-manager';
import { initNativeFunctionHandlers } from './native-functions';
import { WebPermissionHandlers } from './web-permissions';
import { initHttpBasicAuthHandlers } from './http-basic-auth';
export default class AppUpdater {
constructor() {
@ -33,13 +35,7 @@ export default class AppUpdater {
let mainWindow: BrowserWindow | null = null;
ipcMain.on('ipc-example', async (event, arg) => {
const msgTemplate = (pingPong: string) => `IPC test: ${pingPong}`;
console.log(msgTemplate(arg));
event.reply('ipc-example', msgTemplate('pong'));
});
ipcMain.handle('app-meta', async () => {
ipcMain.handle(IPC_MAIN_CHANNELS.APP_META, async () => {
return {
webviewPreloadPath: app.isPackaged
? path.join(__dirname, 'preload-webview.js')
@ -112,6 +108,7 @@ const createWindow = async () => {
},
});
initDevtoolsHandlers(mainWindow);
initHttpBasicAuthHandlers(mainWindow);
const webPermissionHandlers = WebPermissionHandlers(mainWindow);
mainWindow.loadURL(resolveHtmlPath('index.html'));

View file

@ -1,7 +1,5 @@
import { ipcRenderer } from 'electron';
export type Channels = 'ipc-example' | 'app-meta';
console.log('Preload main');
const documentBodyInit = () => {

View file

@ -1,7 +1,6 @@
import { Channels } from 'common/constants';
import { contextBridge, ipcRenderer, IpcRendererEvent } from 'electron';
export type Channels = 'ipc-example' | 'app-meta';
contextBridge.exposeInMainWorld('electron', {
ipcRenderer: {
sendMessage<T>(channel: Channels, args: T[]) {

View file

@ -1,4 +1,5 @@
import { BrowserWindow, ipcMain } from 'electron';
import { IPC_MAIN_CHANNELS } from '../../common/constants';
import store from '../../store';
export interface PermissionRequestArg {
@ -102,7 +103,7 @@ class PermissionsManager {
this.callbacks[key] = [];
}
};
const PERMISSION_RESPONSE_CHANNEL = 'permission-response';
const PERMISSION_RESPONSE_CHANNEL = IPC_MAIN_CHANNELS.PERMISSION_RESPONSE;
if (ipcMain.listeners(PERMISSION_RESPONSE_CHANNEL).length === 0) {
ipcMain.handle(PERMISSION_RESPONSE_CHANNEL, handler);
}
@ -153,7 +154,7 @@ class PermissionsManager {
if (previousState === PERMISSION_STATE.PROMPT) {
return;
}
this.mainWindow.webContents.send('permission-request', {
this.mainWindow.webContents.send(IPC_MAIN_CHANNELS.PERMISSION_REQUEST, {
permission: type,
requestingOrigin: origin,
});

View file

@ -0,0 +1,75 @@
import { IPC_MAIN_CHANNELS } from 'common/constants';
import { AuthInfo } from 'electron';
import { AuthResponseArgs } from 'main/http-basic-auth';
import { useEffect, useState } from 'react';
import Button from 'renderer/components/Button';
import Input from 'renderer/components/Input';
import Modal from 'renderer/components/Modal';
interface Props {
isOpen: boolean;
onClose: () => void;
authInfo: AuthInfo | null;
}
const AuthModal = ({ isOpen, onClose, authInfo }: Props) => {
const [username, setUsername] = useState<string>('');
const [password, setPassword] = useState<string>('');
useEffect(() => {
if (!isOpen) {
setUsername('');
setPassword('');
}
}, [isOpen]);
const onSubmit = (proceed: boolean) => {
if (authInfo == null) {
return;
}
window.electron.ipcRenderer.sendMessage<AuthResponseArgs>(
IPC_MAIN_CHANNELS.AUTH_RESPONSE,
{
authInfo,
username: proceed ? username : '',
password: proceed ? password : '',
}
);
onClose();
};
return (
<Modal isOpen={isOpen} onClose={onClose} title="Http Authentication">
<div className="flex flex-col gap-4">
<p>
Authentication request for{' '}
<span className="font-bold">{authInfo?.host}</span>
</p>
<div className="flex w-[420px] flex-col gap-2">
<Input
label="Username"
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
<Input
label="Password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</div>
<div className="flex flex-row justify-end gap-2">
<Button className="px-2" onClick={() => onSubmit(false)}>
Cancel
</Button>
<Button className="px-2" onClick={() => onSubmit(true)} isActive>
Proceed
</Button>
</div>
</div>
</Modal>
);
};
export default AuthModal;

View file

@ -1,11 +1,14 @@
import { Icon } from '@iconify/react';
import cx from 'classnames';
import { IPC_MAIN_CHANNELS } 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 { useDispatch, useSelector } from 'react-redux';
import Button from 'renderer/components/Button';
import { webViewPubSub } from 'renderer/lib/pubsub';
import { selectAddress, setAddress } from 'renderer/store/features/renderer';
import AuthModal from './AuthModal';
import SuggestionList from './SuggestionList';
export const ADDRESS_BAR_EVENTS = {
@ -28,6 +31,7 @@ const AddressBar = () => {
const [deleteCacheLoading, setDeleteCacheLoading] = useState<boolean>(false);
const [permissionRequest, setPermissionRequest] =
useState<PermissionRequestArg | null>(null);
const [authRequest, setAuthRequest] = useState<AuthRequestArgs | null>(null);
const address = useSelector(selectAddress);
const dispatch = useDispatch();
@ -41,13 +45,22 @@ const AddressBar = () => {
useEffect(() => {
window.electron.ipcRenderer.on<PermissionRequestArg>(
'permission-request',
IPC_MAIN_CHANNELS.PERMISSION_REQUEST,
(args) => {
setPermissionRequest(args);
}
);
window.electron.ipcRenderer.on<AuthRequestArgs>(
IPC_MAIN_CHANNELS.AUTH_REQUEST,
(args) => {
setAuthRequest(args);
}
);
return () => {
window.electron.ipcRenderer.removeAllListeners('permission-request');
window.electron.ipcRenderer.removeAllListeners(
IPC_MAIN_CHANNELS.PERMISSION_REQUEST
);
};
}, []);
@ -61,7 +74,7 @@ const AddressBar = () => {
if (!permissionRequest) {
return;
}
window.electron.ipcRenderer.invoke('permission-response', {
window.electron.ipcRenderer.invoke(IPC_MAIN_CHANNELS.PERMISSION_RESPONSE, {
permissionRequest,
allow,
});
@ -116,99 +129,106 @@ const AddressBar = () => {
const isHomepage = address === homepage;
return (
<div className="relative z-10 w-full flex-grow">
<div className="absolute top-2 left-2 mr-2 flex flex-col items-start">
<Icon icon="mdi:web" className="text-gray-500" />
{permissionRequest != null ? (
<div className="z-40 mt-4 flex w-96 flex-col gap-8 rounded bg-white p-6 shadow-lg ring-1 ring-slate-500 !ring-opacity-40 focus:outline-none dark:bg-slate-900 dark:ring-white dark:!ring-opacity-40">
<span>
{permissionRequest.requestingOrigin} requests permission for:{' '}
<br />
<span className="flex justify-center font-bold capitalize">
{permissionRequest.permission}
<>
<div className="relative z-10 w-full flex-grow">
<div className="absolute top-2 left-2 mr-2 flex flex-col items-start">
<Icon icon="mdi:web" className="text-gray-500" />
{permissionRequest != null ? (
<div className="z-40 mt-4 flex w-96 flex-col gap-8 rounded bg-white p-6 shadow-lg ring-1 ring-slate-500 !ring-opacity-40 focus:outline-none dark:bg-slate-900 dark:ring-white dark:!ring-opacity-40">
<span>
{permissionRequest.requestingOrigin} requests permission for:{' '}
<br />
<span className="flex justify-center font-bold capitalize">
{permissionRequest.permission}
</span>
</span>
</span>
<div className="flex justify-end">
<div className="flex w-1/2 justify-around">
<Button
onClick={() => {
permissionReqClickHandler(false);
}}
isActionButton
>
Block
</Button>
<Button
onClick={() => {
permissionReqClickHandler(true);
}}
isActionButton
>
Allow
</Button>
<div className="flex justify-end">
<div className="flex w-1/2 justify-around">
<Button
onClick={() => {
permissionReqClickHandler(false);
}}
isActionButton
>
Block
</Button>
<Button
onClick={() => {
permissionReqClickHandler(true);
}}
isActionButton
>
Allow
</Button>
</div>
</div>
</div>
</div>
) : null}
</div>
<input
ref={inputRef}
type="text"
className={cx(
'w-full text-ellipsis rounded-full px-2 py-1 pl-8 pr-20 dark:bg-slate-900',
{
'rounded-tl-lg rounded-tr-lg rounded-bl-none rounded-br-none outline-none':
isSuggesting,
}
)}
value={typedAddress}
onChange={(e) => setTypedAddress(e.target.value)}
onKeyDown={handleKeyDown}
onBlur={() => {
setTimeout(() => {
setIsSuggesting(false);
}, 100);
}}
/>
<div className="absolute inset-y-0 right-0 mr-2 flex items-center">
<Button
className="rounded-full"
onClick={deleteStorage}
isLoading={deleteStorageLoading}
title="Delete Storage"
>
<Icon icon="mdi:database-remove-outline" />
</Button>
<Button
className="rounded-full"
onClick={deleteCookies}
isLoading={deleteCookiesLoading}
title="Delete Cookies"
>
<Icon icon="mdi:cookie-remove-outline" />
</Button>
<Button
className="rounded-full"
onClick={deleteCache}
isLoading={deleteCacheLoading}
title="Clear Cache"
>
<Icon icon="mdi:wifi-remove" />
</Button>
<Button
className={cx('rounded-full', {
'text-blue-500': isHomepage,
})}
onClick={() => setHomepage(address)}
title="Homepage"
>
<Icon icon={isHomepage ? 'mdi:home' : 'mdi:home-outline'} />
</Button>
</div>
{isSuggesting ? (
<SuggestionList match={typedAddress} onEnter={onEnter} />
) : null}
</div>
<input
ref={inputRef}
type="text"
className={cx(
'w-full text-ellipsis rounded-full px-2 py-1 pl-8 pr-20 dark:bg-slate-900',
{
'rounded-tl-lg rounded-tr-lg rounded-bl-none rounded-br-none outline-none':
isSuggesting,
}
)}
value={typedAddress}
onChange={(e) => setTypedAddress(e.target.value)}
onKeyDown={handleKeyDown}
onBlur={() => {
setTimeout(() => {
setIsSuggesting(false);
}, 100);
}}
<AuthModal
isOpen={authRequest != null}
onClose={() => setAuthRequest(null)}
authInfo={authRequest}
/>
<div className="absolute inset-y-0 right-0 mr-2 flex items-center">
<Button
className="rounded-full"
onClick={deleteStorage}
isLoading={deleteStorageLoading}
title="Delete Storage"
>
<Icon icon="mdi:database-remove-outline" />
</Button>
<Button
className="rounded-full"
onClick={deleteCookies}
isLoading={deleteCookiesLoading}
title="Delete Cookies"
>
<Icon icon="mdi:cookie-remove-outline" />
</Button>
<Button
className="rounded-full"
onClick={deleteCache}
isLoading={deleteCacheLoading}
title="Clear Cache"
>
<Icon icon="mdi:wifi-remove" />
</Button>
<Button
className={cx('rounded-full', {
'text-blue-500': isHomepage,
})}
onClick={() => setHomepage(address)}
title="Homepage"
>
<Icon icon={isHomepage ? 'mdi:home' : 'mdi:home-outline'} />
</Button>
</div>
{isSuggesting ? (
<SuggestionList match={typedAddress} onEnter={onEnter} />
) : null}
</div>
</>
);
};

View file

@ -1,17 +1,12 @@
import { IPC_MAIN_CHANNELS } from 'common/constants';
import { createRoot } from 'react-dom/client';
import App from './AppContent';
const container = document.getElementById('root')!;
const root = createRoot(container);
// calling IPC exposed from preload script
window.electron.ipcRenderer.once('ipc-example', (arg) => {
// eslint-disable-next-line no-console
console.log(arg);
});
window.electron.ipcRenderer.sendMessage('ipc-example', ['ping']);
window.electron.ipcRenderer
.invoke('app-meta', [])
.invoke(IPC_MAIN_CHANNELS.APP_META, [])
.then((arg: any) => {
window.responsively = { webviewPreloadPath: arg.webviewPreloadPath };
return root.render(<App />);

View file

@ -1,10 +1,10 @@
import { Channels } from 'main/preload';
import type { Channels } from 'common/constants';
declare global {
interface Window {
electron: {
ipcRenderer: {
sendMessage<T>(channel: Channels, args: T[]): void;
sendMessage<T>(channel: Channels, ...args: T[]): void;
on<T>(
channel: string,
func: (...args: T[]) => void