mirror of
https://github.com/tlsnotary/tlsn-extension.git
synced 2026-01-09 15:18:09 -05:00
Developer Mode (#190)
* feat: adding developer mode toggle to add and display plugins * chore: lint * chore: cleanup
This commit is contained in:
@@ -5,7 +5,12 @@ import React, {
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { fetchPluginHashes, removePlugin, runPlugin } from '../../utils/rpc';
|
||||
import {
|
||||
fetchPluginHashes,
|
||||
removePlugin,
|
||||
runPlugin,
|
||||
addPlugin,
|
||||
} from '../../utils/rpc';
|
||||
import { usePluginHashes } from '../../reducers/plugins';
|
||||
import {
|
||||
getPluginConfig,
|
||||
@@ -31,20 +36,78 @@ export function PluginList({
|
||||
className,
|
||||
unremovable,
|
||||
onClick,
|
||||
showAddButton = false,
|
||||
}: {
|
||||
className?: string;
|
||||
unremovable?: boolean;
|
||||
onClick?: (hash: string) => void;
|
||||
showAddButton?: boolean;
|
||||
}): ReactElement {
|
||||
const hashes = usePluginHashes();
|
||||
const [uploading, setUploading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
fetchPluginHashes();
|
||||
}, []);
|
||||
|
||||
const handleFileUpload = useCallback(
|
||||
async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
if (!file.name.endsWith('.wasm')) {
|
||||
alert('Please select a .wasm file');
|
||||
return;
|
||||
}
|
||||
|
||||
setUploading(true);
|
||||
try {
|
||||
const arrayBuffer = await file.arrayBuffer();
|
||||
const hex = Buffer.from(arrayBuffer).toString('hex');
|
||||
const url = `file://${file.name}`;
|
||||
|
||||
await addPlugin(hex, url);
|
||||
await fetchPluginHashes();
|
||||
} catch (error: any) {
|
||||
alert(`Failed to add plugin: ${error.message}`);
|
||||
} finally {
|
||||
setUploading(false);
|
||||
e.target.value = '';
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={classNames('flex flex-col flex-nowrap gap-1', className)}>
|
||||
{!hashes.length && (
|
||||
{showAddButton && (
|
||||
<div className="relative">
|
||||
<input
|
||||
type="file"
|
||||
accept=".wasm"
|
||||
onChange={handleFileUpload}
|
||||
disabled={uploading}
|
||||
className="absolute inset-0 w-full h-full opacity-0 cursor-pointer z-10"
|
||||
/>
|
||||
<button
|
||||
className="flex flex-row items-center justify-center gap-2 p-3 border-2 border-dashed border-slate-300 rounded-lg text-slate-500 hover:text-slate-700 hover:border-slate-400 transition-colors cursor-pointer w-full"
|
||||
disabled={uploading}
|
||||
>
|
||||
{uploading ? (
|
||||
<>
|
||||
<Icon fa="fa-solid fa-spinner" className="animate-spin" />
|
||||
<span>Adding Plugin...</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Icon fa="fa-solid fa-plus" />
|
||||
<span>Add Plugin (.wasm file)</span>
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{!hashes.length && !showAddButton && (
|
||||
<div className="flex flex-col items-center justify-center text-slate-400 cursor-default select-none">
|
||||
<div>No available plugins</div>
|
||||
</div>
|
||||
|
||||
@@ -44,8 +44,9 @@ export default function SidePanel(): ReactElement {
|
||||
|
||||
switch (type) {
|
||||
case SidePanelActionTypes.execute_plugin_request: {
|
||||
setConfig(await getPluginConfigByUrl(data.pluginUrl));
|
||||
setUrl(data.pluginUrl);
|
||||
const pluginIdentifier = data.pluginUrl || data.pluginHash;
|
||||
setConfig(await getPluginConfigByUrl(pluginIdentifier));
|
||||
setUrl(pluginIdentifier);
|
||||
setParams(data.pluginParams);
|
||||
setStarted(true);
|
||||
break;
|
||||
|
||||
@@ -11,17 +11,23 @@ import History from '../History';
|
||||
import './index.scss';
|
||||
import Requests from '../Requests';
|
||||
import { fetchPluginHashes } from '../../utils/rpc';
|
||||
import { PluginList } from '../../components/PluginList';
|
||||
import { getDeveloperMode } from '../../utils/storage';
|
||||
|
||||
export default function Home(props: {
|
||||
tab?: 'history' | 'network';
|
||||
}): ReactElement {
|
||||
const [error, showError] = useState('');
|
||||
const [tab, setTab] = useState<'history' | 'network'>(props.tab || 'history');
|
||||
const [tab, setTab] = useState<'history' | 'network' | 'plugins'>(
|
||||
props.tab || 'history',
|
||||
);
|
||||
const scrollableContent = useRef<HTMLDivElement | null>(null);
|
||||
const [shouldFix, setFix] = useState(false);
|
||||
const [developerMode, setDeveloperMode] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
fetchPluginHashes();
|
||||
getDeveloperMode().then(setDeveloperMode);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -71,10 +77,24 @@ export default function Home(props: {
|
||||
>
|
||||
History
|
||||
</TabSelector>
|
||||
{developerMode && (
|
||||
<TabSelector
|
||||
onClick={() => setTab('plugins')}
|
||||
selected={tab === 'plugins'}
|
||||
>
|
||||
Plugins
|
||||
</TabSelector>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-grow">
|
||||
{tab === 'history' && <History />}
|
||||
{tab === 'network' && <Requests shouldFix={shouldFix} />}
|
||||
{tab === 'plugins' && (
|
||||
<PluginList
|
||||
className="p-2 overflow-y-auto"
|
||||
showAddButton={developerMode}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -19,6 +19,8 @@ import {
|
||||
LOGGING_FILTER_KEY,
|
||||
getRendezvousApi,
|
||||
RENDEZVOUS_API_LS_KEY,
|
||||
getDeveloperMode,
|
||||
DEVELOPER_MODE_LS_KEY,
|
||||
} from '../../utils/storage';
|
||||
import {
|
||||
EXPLORER_API,
|
||||
@@ -41,6 +43,7 @@ export default function Options(): ReactElement {
|
||||
const [maxReceived, setMaxReceived] = useState(MAX_RECV);
|
||||
const [loggingLevel, setLoggingLevel] = useState<LoggingLevel>('Info');
|
||||
const [rendezvous, setRendezvous] = useState(RENDEZVOUS_API);
|
||||
const [developerMode, setDeveloperMode] = useState(false);
|
||||
|
||||
const [dirty, setDirty] = useState(false);
|
||||
const [shouldReload, setShouldReload] = useState(false);
|
||||
@@ -61,6 +64,7 @@ export default function Options(): ReactElement {
|
||||
(async () => {
|
||||
setNotary(await getNotaryApi());
|
||||
setProxy(await getProxyApi());
|
||||
setDeveloperMode(await getDeveloperMode());
|
||||
})();
|
||||
}, []);
|
||||
|
||||
@@ -85,6 +89,7 @@ export default function Options(): ReactElement {
|
||||
await set(MAX_RECEIVED_LS_KEY, maxReceived.toString());
|
||||
await set(LOGGING_FILTER_KEY, loggingLevel);
|
||||
await set(RENDEZVOUS_API_LS_KEY, rendezvous);
|
||||
await set(DEVELOPER_MODE_LS_KEY, developerMode.toString());
|
||||
setDirty(false);
|
||||
},
|
||||
[
|
||||
@@ -94,6 +99,7 @@ export default function Options(): ReactElement {
|
||||
maxReceived,
|
||||
loggingLevel,
|
||||
rendezvous,
|
||||
developerMode,
|
||||
shouldReload,
|
||||
],
|
||||
);
|
||||
@@ -157,6 +163,8 @@ export default function Options(): ReactElement {
|
||||
proxy={proxy}
|
||||
setProxy={setProxy}
|
||||
setDirty={setDirty}
|
||||
developerMode={developerMode}
|
||||
setDeveloperMode={setDeveloperMode}
|
||||
/>
|
||||
<div className="justify-left px-2 pt-3 gap-2">
|
||||
<button className="font-bold" onClick={onAdvanced}>
|
||||
@@ -255,8 +263,18 @@ function NormalOptions(props: {
|
||||
proxy: string;
|
||||
setProxy: (value: string) => void;
|
||||
setDirty: (value: boolean) => void;
|
||||
developerMode: boolean;
|
||||
setDeveloperMode: (value: boolean) => void;
|
||||
}) {
|
||||
const { notary, setNotary, proxy, setProxy, setDirty } = props;
|
||||
const {
|
||||
notary,
|
||||
setNotary,
|
||||
proxy,
|
||||
setProxy,
|
||||
setDirty,
|
||||
developerMode,
|
||||
setDeveloperMode,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -288,6 +306,33 @@ function NormalOptions(props: {
|
||||
<div className="font-semibold">Explorer URL</div>
|
||||
<div className="input border bg-slate-100">{EXPLORER_API}</div>
|
||||
</div>
|
||||
<div className="flex flex-row items-center py-3 px-2 gap-2">
|
||||
<div className="font-semibold">Developer Mode</div>
|
||||
<div className="relative inline-block w-9 h-5">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="developer-mode"
|
||||
checked={developerMode}
|
||||
onChange={(e) => {
|
||||
setDeveloperMode(e.target.checked);
|
||||
setDirty(true);
|
||||
}}
|
||||
className="sr-only"
|
||||
/>
|
||||
<label
|
||||
htmlFor="developer-mode"
|
||||
className={`block h-5 rounded-full cursor-pointer transition-all duration-300 ease-in-out ${
|
||||
developerMode ? 'bg-blue-500' : 'bg-gray-300'
|
||||
}`}
|
||||
>
|
||||
<span
|
||||
className={`absolute top-0.5 left-0.5 w-4 h-4 bg-white rounded-full shadow-sm transform transition-all duration-300 ease-in-out ${
|
||||
developerMode ? 'translate-x-4' : 'translate-x-0'
|
||||
}`}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ export const MAX_SENT_LS_KEY = 'max-sent';
|
||||
export const MAX_RECEIVED_LS_KEY = 'max-received';
|
||||
export const LOGGING_FILTER_KEY = 'logging-filter-2';
|
||||
export const RENDEZVOUS_API_LS_KEY = 'rendezvous-api';
|
||||
export const DEVELOPER_MODE_LS_KEY = 'developer-mode';
|
||||
|
||||
export async function set(key: string, value: string) {
|
||||
return chrome.storage.sync.set({ [key]: value });
|
||||
@@ -43,3 +44,8 @@ export async function getLoggingFilter(): Promise<LoggingLevel> {
|
||||
export async function getRendezvousApi(): Promise<string> {
|
||||
return await get(RENDEZVOUS_API_LS_KEY, RENDEZVOUS_API);
|
||||
}
|
||||
|
||||
export async function getDeveloperMode(): Promise<boolean> {
|
||||
const value = await get(DEVELOPER_MODE_LS_KEY, 'false');
|
||||
return value === 'true';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user