mirror of
https://github.com/yaklang/yaklang-chrome-extension.git
synced 2026-09-26 21:21:53 +08:00
feat(browser): expose proxy state and browser identity (#9)
* feat(proxy): reflect and release browser proxy control * feat(browser): report browser product identity
This commit is contained in:
@@ -16,6 +16,7 @@ import {
|
||||
import { cookieKey, cookieRemovalInput } from '@/features/cookies/presentation';
|
||||
import { AutoSwitchView } from '@/features/proxy/ui/AutoSwitchView';
|
||||
import { ProxyProfilesView } from '@/features/proxy/ui/ProxyProfilesView';
|
||||
import { useProxyStatus } from '@/features/proxy/ui/ProxyStatusBar';
|
||||
import { RuleSourcesView } from '@/features/proxy/ui/RuleSourcesView';
|
||||
import { RecordingWorkspace } from '@/features/browser-recording/RecordingWorkspace';
|
||||
import { AuthorizationTestingWorkspace } from '@/features/authorization-testing/ui/AuthorizationTestingWorkspace';
|
||||
@@ -337,7 +338,7 @@ function ActivityLog({ run, busy }: { run: (task: () => Promise<void>, success?:
|
||||
}
|
||||
|
||||
function Overview({ state, bridge, tab, navigate, run, busy }: { state: ExtensionState; bridge: BridgeStatus; tab?: ActiveTabInfo; navigate: (value: Section) => void; run: (task: () => Promise<void>, success?: string) => Promise<void>; busy: boolean }) {
|
||||
const activeProxy = state.proxyProfiles.find((profile) => profile.id === state.activeProxyId)?.name || (state.activeProxyId === 'auto' ? '自动切换' : '未知');
|
||||
const activeProxy = useProxyStatus(state).label;
|
||||
const [runtime, setRuntime] = useState<AgentRuntime>({ state: 'idle', updatedAt: Date.now(), actions: [] });
|
||||
const [network, setNetwork] = useState<NetworkCaptureStatus>();
|
||||
const [loginContext, setLoginContext] = useState<PageContext>();
|
||||
|
||||
@@ -206,7 +206,9 @@
|
||||
.popup-footer { margin-top: auto; padding: 10px 14px 12px; border-top: 1px solid var(--border); background: var(--surface); }
|
||||
.popup-capture { width: 100%; height: 36px; border-radius: 7px; font-size: var(--text-md); box-shadow: 0 1px 0 color-mix(in srgb, var(--primary-strong) 55%, transparent); }
|
||||
.popup-notice { display: block; margin-top: 6px; overflow: hidden; color: var(--muted); font-size: var(--text-sm); line-height: 16px; text-align: center; white-space: nowrap; text-overflow: ellipsis; }
|
||||
.popup-global-notice { position: absolute; z-index: 20; left: 50%; bottom: 54px; max-width: calc(100% - 28px); padding: 7px 11px; overflow: hidden; border: 1px solid var(--border); border-radius: 999px; background: var(--foreground); color: var(--surface); box-shadow: var(--shadow-md); font-size: var(--text-xs); line-height: 16px; text-overflow: ellipsis; white-space: nowrap; pointer-events: none; transform: translateX(-50%); animation: popup-content-in .16s ease-out; }
|
||||
/* Keep horizontal centering independent of the entrance animation's transform. */
|
||||
.popup-global-notice { position: absolute; z-index: 20; left: 50%; bottom: 54px; max-width: calc(100% - 28px); padding: 7px 11px; overflow: hidden; border: 1px solid var(--border); border-radius: 999px; background: var(--foreground); color: var(--surface); box-shadow: var(--shadow-md); font-size: var(--text-xs); line-height: 16px; text-overflow: ellipsis; white-space: nowrap; pointer-events: none; translate: -50% 0; animation: popup-content-in .16s ease-out; }
|
||||
@media (prefers-reduced-motion: reduce) { .popup-global-notice { animation: none; } }
|
||||
|
||||
.popup-loading { width: 390px; height: 230px; display: flex; align-items: center; justify-content: center; gap: 9px; color: var(--muted); background: var(--background); font-size: var(--text-md); }
|
||||
.spin { animation: spin .8s linear infinite; }
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Braces, ChevronRight, Cookie, Network, Radio, ShieldCheck, UserRoundCog
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { request } from '@/platform/messaging/runtime';
|
||||
import { useProxyStatus } from '@/features/proxy/ui/ProxyStatusBar';
|
||||
import { READ_CAPABILITY_SCOPES } from '@/protocol/capabilities';
|
||||
import type { ActiveTabInfo, ExtensionState, UserAgentResolution } from '@/types/models';
|
||||
|
||||
@@ -25,9 +26,7 @@ interface OverviewQuickViewProps {
|
||||
export function OverviewQuickView({
|
||||
state, tab, grantActive, busy, run, setState, cookieCount, uaResolution, onNavigate, onOpenContext, onCapture,
|
||||
}: OverviewQuickViewProps) {
|
||||
const activeProxy = state.activeProxyId === 'auto'
|
||||
? '自动切换'
|
||||
: state.proxyProfiles.find((profile) => profile.id === state.activeProxyId)?.name || '未选择';
|
||||
const activeProxy = useProxyStatus(state).label;
|
||||
const targetAvailable = Boolean(tab?.url?.startsWith('http'));
|
||||
|
||||
return <section className="popup-overview-view">
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useEffect, useMemo, useState } from 'react';
|
||||
import { AlertCircle, Check, ExternalLink, Globe2, LoaderCircle, Network, Route } from 'lucide-react';
|
||||
import { request } from '@/platform/messaging/runtime';
|
||||
import type { ActiveTabInfo, ExtensionState, ProxyProfile, ProxyRulePreview } from '@/types/models';
|
||||
import { ProxyStatusBar, StartupProxyOption, useProxyStatus } from '@/features/proxy/ui/ProxyStatusBar';
|
||||
|
||||
type RunTask = (task: () => Promise<void>, success?: string) => Promise<void>;
|
||||
|
||||
@@ -43,10 +44,11 @@ function routeKindLabel(preview?: ProxyRulePreview): string {
|
||||
}
|
||||
|
||||
export function ProxyQuickView({ state, setState, busy, run, tab, onOpenFull }: ProxyQuickViewProps) {
|
||||
const status = useProxyStatus(state);
|
||||
const [preview, setPreview] = useState<ProxyRulePreview>();
|
||||
const currentHostname = hostname(tab?.url);
|
||||
const autoActive = state.activeProxyId === 'auto';
|
||||
const activeProfile = state.proxyProfiles.find((profile) => profile.id === state.activeProxyId);
|
||||
const autoActive = status.activeProfileId === 'auto';
|
||||
const activeProfile = state.proxyProfiles.find((profile) => profile.id === status.activeProfileId);
|
||||
const routableProfiles = useMemo(
|
||||
() => state.proxyProfiles.filter((profile) => profile.kind === 'direct' || profile.kind === 'fixed_servers'),
|
||||
[state.proxyProfiles],
|
||||
@@ -130,7 +132,7 @@ export function ProxyQuickView({ state, setState, busy, run, tab, onOpenFull }:
|
||||
};
|
||||
|
||||
const effectiveProfile = state.proxyProfiles.find((profile) => profile.id === preview?.effectiveProfileId);
|
||||
const activeModeName = autoActive ? '自动切换' : activeProfile?.name || '未选择';
|
||||
const activeModeName = autoActive ? '自动切换' : status.label;
|
||||
const siteHint = !autoActive
|
||||
? `当前使用“${activeModeName}”;选择网站出口后将启用自动切换。`
|
||||
: siteTarget === AUTOMATIC_TARGET
|
||||
@@ -142,13 +144,14 @@ export function ProxyQuickView({ state, setState, busy, run, tab, onOpenFull }:
|
||||
const routeKindText = autoActive ? routeKindLabel(preview) : '全局模式';
|
||||
|
||||
return <section className="popup-view popup-tool-view popup-proxy-view">
|
||||
<ProxyStatusBar status={status} />
|
||||
{currentHostname ? <section className="popup-site-router" aria-label="当前站点路由">
|
||||
<div className="popup-site-router__heading">
|
||||
<div><Globe2 size={16} /><span><small>当前站点</small><strong title={currentHostname}>{currentHostname}</strong></span></div>
|
||||
<i className={routeKind}>{routeKindText}</i>
|
||||
</div>
|
||||
<div className="popup-site-decision" title={autoActive ? preview?.matchedCondition : activeModeName}>
|
||||
<span>{routeLabel}</span><i>→</i><strong>{routeProfile?.name || '—'}</strong>
|
||||
<span>{routeLabel}</span><i>→</i><strong>{routeProfile?.name || status.label}</strong>
|
||||
</div>
|
||||
<div className="popup-site-picker">
|
||||
<label htmlFor="popup-site-proxy">网站出口 <span>选择后立即生效</span></label>
|
||||
@@ -168,13 +171,14 @@ export function ProxyQuickView({ state, setState, busy, run, tab, onOpenFull }:
|
||||
|
||||
<div className="popup-mode-heading"><span><strong>浏览器模式</strong><small>全局切换,不会创建站点规则</small></span><i>{activeModeName}</i></div>
|
||||
<div className="popup-proxy-list popup-proxy-list--view" role="radiogroup" aria-label="浏览器代理模式">
|
||||
<StartupProxyOption state={state} status={status} setState={setState} run={run} busy={busy} />
|
||||
<button role="radio" aria-checked={autoActive} className={autoActive ? 'is-active' : ''} disabled={busy} onClick={() => void switchAuto()}>
|
||||
<span className="popup-mode-icon"><Route size={15} /></span>
|
||||
<span><strong>自动切换</strong><small>{state.proxyRules.filter((rule) => rule.enabled).length} 条手动 · {sourceRuleCount.toLocaleString()} 条订阅</small></span>
|
||||
{state.proxyRuntime.dirty ? <em>待应用</em> : autoActive ? <Check size={14} /> : null}
|
||||
</button>
|
||||
{state.proxyProfiles.map((profile) => {
|
||||
const active = state.activeProxyId === profile.id;
|
||||
const active = status.activeProfileId === profile.id;
|
||||
return <button key={profile.id} role="radio" aria-checked={active} className={active ? 'is-active' : ''} disabled={busy} onClick={() => void run(async () => setState(await request('proxy.switch', { id: profile.id })), `${profile.name} 已作为全局模式启用`)}>
|
||||
<span className="popup-mode-icon"><Network size={15} /></span>
|
||||
<span><strong>{profile.name}</strong><small>{proxyDetail(profile)}</small></span>
|
||||
|
||||
@@ -25,6 +25,9 @@ async function bootstrap(): Promise<void> {
|
||||
|
||||
await request('bridge.managed-instance.bind', {
|
||||
manager: manager as 'ytray' | 'yakit', instanceId, badge,
|
||||
browserName: query.get('browserName') || undefined,
|
||||
browserVersion: query.get('browserVersion') || undefined,
|
||||
startupProxy: query.get('startupProxy') || undefined,
|
||||
});
|
||||
|
||||
const current = await browser.tabs.getCurrent();
|
||||
|
||||
Reference in New Issue
Block a user