add proxy switch demo

This commit is contained in:
go0p
2025-03-04 16:07:14 +08:00
parent 692ab7760e
commit 8c30607514
28 changed files with 12458 additions and 28 deletions
+1 -1
View File
@@ -15,7 +15,7 @@ build.pem
# production # production
/build /build
/2.5.21_0
# misc # misc
.DS_Store .DS_Store
.env.local .env.local
+4 -1
View File
@@ -1,10 +1,13 @@
import {ActionType, injectScriptAndSendMessage, WebSocketManager} from './socket.js'; import {ActionType, injectScriptAndSendMessage, WebSocketManager} from './socket.js';
import { setupProxyHandlers } from './proxy.js';
import { ProxyActionType } from './types/action.js';
console.info("Chrome Extenstion Background is loaded") console.info("Chrome Extenstion Background is loaded")
const websocketManager = new WebSocketManager(); const websocketManager = new WebSocketManager();
// 设置代理处理器
setupProxyHandlers();
chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) { chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) {
console.log("msg", msg) console.log("msg", msg)
+7 -3
View File
@@ -3,6 +3,7 @@
return; return;
} }
window.contentScriptInjected = true; window.contentScriptInjected = true;
window.badgeCount = 0;
// 检查并插入 CSS 样式 // 检查并插入 CSS 样式
const styleId = 'injected-css-style'; const styleId = 'injected-css-style';
if (!document.getElementById(styleId)) { if (!document.getElementById(styleId)) {
@@ -38,18 +39,21 @@
script.remove(); script.remove();
}; };
(document.head || document.documentElement).appendChild(script); (document.head || document.documentElement).appendChild(script);
window.addEventListener('message', function onMessage(event) { window.addEventListener('message', async function onMessage(event) {
if (event.source !== window || event.data.type !== 'FROM_INJECT_JS') { if (event.source !== window || event.data.type !== 'FROM_INJECT_JS') {
return; return;
} }
window.removeEventListener('message', onMessage); window.removeEventListener('message', onMessage);
// Send the result to the background script window.badgeCount += 1;
// chrome.runtime.sendMessage({ action: 'yakit_to_extension_page', result: event.data.result });
// 直接向向发送端返回结果 // 直接向向发送端返回结果
sendResponse({action: 'yakit_to_extension_page', result: event.data.result}); sendResponse({action: 'yakit_to_extension_page', result: event.data.result});
// Send updated badge count to background script
await chrome.runtime.sendMessage({action: 'yakit_badge', data: window.badgeCount.toString()});
}); });
return true; return true;
} }
}); });
})() })()
+17 -10
View File
@@ -3,36 +3,43 @@
"name": "Yakit Chrome Endpoint", "name": "Yakit Chrome Endpoint",
"version": "1.0", "version": "1.0",
"description": "A Endpoint for Yakit MITM or more", "description": "A Endpoint for Yakit MITM or more",
"options_ui": {
"page": "proxy/options.html",
"open_in_tab": true
},
"action": { "action": {
"default_popup": "index.html", "default_title": "Click to open panel",
"default_icon": { "default_icon": {
"16": "/images/icon16.png", "16": "/images/icon16.png",
"48": "/images/icon48.png", "48": "/images/icon48.png",
"128": "/images/icon128.png" "128": "/images/icon128.png"
} }
}, },
"side_panel": {
"default_path" : "index.html"
},
"background": { "background": {
"service_worker": "background.js", "service_worker": "background.js",
"type": "module" "type": "module"
}, },
"permissions": [ "permissions": [
"webNavigation",
"activeTab",
"scripting",
"tabs",
"proxy", "proxy",
"storage", "storage",
"webRequest" "sidePanel"
], ],
"host_permissions": [ "host_permissions": [
"<all_urls>" "*://*/*"
], ],
"web_accessible_resources": [ "web_accessible_resources": [
{ {
"resources": ["inject.js"], "resources": [
"matches": ["<all_urls>"], "types/*.js",
"use_dynamic_url": true "proxy/*.js",
"socket.js",
"proxy.js"
],
"matches": ["<all_urls>"]
} }
], ],
"icons": { "icons": {
+214
View File
@@ -0,0 +1,214 @@
import { ProxyManager } from './proxy/proxy-manager.js';
import { ProxySettings } from './proxy/proxy-settings.js';
import { ProxyAuth } from './proxy/proxy-auth.js';
import { ProxyActionType } from './types/action.js';
// 记录代理日志
async function logProxyRequest(details, proxyConfig, error = null) {
const log = {
id: Date.now().toString(),
timestamp: Date.now(),
url: details.url,
proxyId: proxyConfig.id,
proxyName: proxyConfig.name,
status: error ? 'error' : 'success',
errorMessage: error?.message
};
const result = await chrome.storage.local.get('proxyLogs');
const logs = result.proxyLogs || [];
const updatedLogs = [log, ...logs].slice(0, 1000);
await chrome.storage.local.set({ proxyLogs: updatedLogs });
}
async function handleSetProxyConfig(config, sendResponse) {
try {
if (!config || !config.host || !config.port) {
sendResponse({
success: false,
error: '无效的代理配置'
});
return;
}
const proxyConfig = {
mode: "fixed_servers",
rules: {
singleProxy: {
scheme: config.scheme || 'http',
host: config.host,
port: parseInt(config.port)
},
bypassList: ["localhost", "127.0.0.1"]
}
};
await chrome.proxy.settings.set({
value: proxyConfig,
scope: 'regular'
});
const settings = await chrome.proxy.settings.get({});
const isSuccess = settings.value.mode === "fixed_servers" &&
settings.value.rules.singleProxy.host === config.host &&
settings.value.rules.singleProxy.port === parseInt(config.port);
if (isSuccess) {
await chrome.storage.local.set({
currentProxy: {
...config,
timestamp: Date.now()
}
});
const result = await chrome.storage.local.get('proxyConfigs');
if (result.proxyConfigs) {
const updatedConfigs = result.proxyConfigs.map(c => ({
...c,
enabled: c.id === config.id
}));
await chrome.storage.local.set({ proxyConfigs: updatedConfigs });
}
console.log('Proxy successfully set:', settings.value);
sendResponse({ success: true });
} else {
console.error('Proxy settings verification failed');
sendResponse({
success: false,
error: '代理设置验证失败'
});
}
} catch (error) {
console.error('Error setting proxy:', error);
sendResponse({
success: false,
error: error.message || '设置代理时发生错误'
});
}
}
async function handleClearProxyConfig(sendResponse) {
try {
await chrome.proxy.settings.clear({
scope: 'regular'
});
await chrome.proxy.settings.set({
value: { mode: "system" },
scope: 'regular'
});
// 只移除当前代理配置,保留代理列表
await chrome.storage.local.remove([
'currentProxy',
'proxyAuthHandlers'
]);
// 更新所有代理的启用状态
const result = await chrome.storage.local.get('proxyConfigs');
if (result.proxyConfigs) {
const updatedConfigs = result.proxyConfigs.map(config => ({
...config,
enabled: false
}));
await chrome.storage.local.set({ proxyConfigs: updatedConfigs });
}
const settings = await chrome.proxy.settings.get({});
const isSuccess = settings.value.mode === "system";
if (isSuccess) {
console.log('Proxy successfully cleared');
sendResponse({ success: true });
} else {
console.error('Failed to clear proxy settings');
sendResponse({
success: false,
error: '无法清除代理设置'
});
}
} catch (error) {
console.error('Error clearing proxy:', error);
sendResponse({
success: false,
error: error.message || '清除代理时发生错误'
});
}
}
async function handleGetProxyStatus(sendResponse) {
try {
const settings = await chrome.proxy.settings.get({});
const currentProxy = await chrome.storage.local.get('currentProxy');
const status = {
enabled: settings.value.mode === "fixed_servers",
config: currentProxy.currentProxy || null,
mode: settings.value.mode
};
console.log('Current proxy status:', status);
sendResponse({
success: true,
data: status
});
} catch (error) {
console.error('Error getting proxy status:', error);
sendResponse({
success: false,
error: error.message || '获取代理状态时发生错误'
});
}
}
// 导出代理处理器设置函数
export function setupProxyHandlers() {
// 设置代理错误处理和认证
ProxyAuth.setupErrorHandler();
ProxyAuth.setupAuthListener();
// 消息监听器
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
console.log("Proxy message:", msg);
switch (msg.action) {
case ProxyActionType.SET_PROXY_CONFIG:
handleSetProxyConfig(msg.config, sendResponse);
return true;
case ProxyActionType.CLEAR_PROXY_CONFIG:
handleClearProxyConfig(sendResponse);
return true;
case ProxyActionType.GET_PROXY_STATUS:
handleGetProxyStatus(sendResponse);
return true;
}
});
// 在扩展启动时初始化
chrome.runtime.onInstalled.addListener(async () => {
try {
// 确保默认配置存在
await ProxySettings.setDefaultConfigs();
// 清除之前的代理设置
await handleClearProxyConfig(() => {});
// 设置认证监听
await ProxyAuth.setupAuthListener();
// 初始化存储
const storage = await chrome.storage.local.get(['proxyConfigs', 'proxyLogs']);
if (!storage.proxyConfigs) {
await chrome.storage.local.set({ proxyConfigs: [] });
}
if (!storage.proxyLogs) {
await chrome.storage.local.set({ proxyLogs: [] });
}
} catch (error) {
console.error('Error during installation:', error);
}
});
}
+160
View File
@@ -0,0 +1,160 @@
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
margin: 0;
padding: 20px;
background-color: #f5f5f5;
}
.container {
max-width: 800px;
margin: 0 auto;
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding-bottom: 20px;
border-bottom: 1px solid #eee;
}
.header h1 {
margin: 0;
font-size: 24px;
color: #333;
}
.proxy-item {
background: white;
border: 1px solid #e8e8e8;
border-radius: 4px;
margin-bottom: 16px;
padding: 16px;
transition: all 0.3s;
}
.proxy-item:hover {
box-shadow: 0 2px 8px rgba(0,0,0,0.09);
}
.proxy-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.proxy-name {
font-size: 14px;
padding: 4px 8px;
border: 1px solid #d9d9d9;
border-radius: 4px;
width: 200px;
}
.proxy-name:focus {
border-color: #40a9ff;
outline: none;
box-shadow: 0 0 0 2px rgba(24,144,255,0.2);
}
.proxy-type-select,
.proxy-scheme,
.proxy-host,
.proxy-port {
width: 100%;
height: 32px;
padding: 4px 11px;
border: 1px solid #d9d9d9;
border-radius: 4px;
transition: all 0.3s;
}
.proxy-type-select:focus,
.proxy-scheme:focus,
.proxy-host:focus,
.proxy-port:focus {
border-color: #40a9ff;
outline: none;
box-shadow: 0 0 0 2px rgba(24,144,255,0.2);
}
.proxy-actions {
display: flex;
gap: 8px;
}
.proxy-content {
display: flex;
flex-direction: column;
gap: 16px;
}
.form-group {
margin-bottom: 16px;
}
.form-group label {
display: block;
margin-bottom: 8px;
color: #666;
}
.btn-primary {
background: #1890ff;
border: none;
color: white;
padding: 4px 15px;
border-radius: 4px;
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
}
.btn-primary:hover {
background: #40a9ff;
}
.btn-secondary {
background: white;
border: 1px solid #d9d9d9;
color: rgba(0,0,0,0.85);
padding: 4px 15px;
border-radius: 4px;
cursor: pointer;
margin-left: 8px;
}
.btn-secondary:hover {
border-color: #40a9ff;
color: #40a9ff;
}
.header-actions {
display: flex;
align-items: center;
}
.delete-btn {
padding: 4px 8px;
background: #ff4d4f;
}
.delete-btn:hover {
background: #ff7875;
}
.pac-script {
font-family: monospace;
min-height: 200px;
}
.options-page {
min-height: 100vh;
}
+11
View File
@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>代理设置</title>
</head>
<body>
<div id="root"></div>
<script src="../options.bundle.js"></script>
</body>
</html>
+170
View File
@@ -0,0 +1,170 @@
console.log('Options page script loaded');
import { ProxySettings } from './proxy-settings.js';
import { ProxyManager } from './proxy-manager.js';
let currentConfigs = [];
document.addEventListener('DOMContentLoaded', async () => {
try {
await initOptionsPage();
setupEventListeners();
} catch (error) {
console.error('Failed to initialize options page:', error);
showError(error.message);
}
});
async function initOptionsPage() {
const result = await ProxySettings.exportSettings();
currentConfigs = result.settings || [];
renderProxyConfigs();
}
function renderProxyConfigs() {
const proxyList = document.getElementById('proxyList');
const template = document.getElementById('proxyItemTemplate');
// 清除除了直接连接以外的所有配置
const items = proxyList.querySelectorAll('.proxy-item:not([data-id="direct"])');
items.forEach(item => item.remove());
// 渲染其他代理配置
currentConfigs.forEach(config => {
if (config.id === 'direct') return; // 跳过直接连接
const clone = template.content.cloneNode(true);
const proxyItem = clone.querySelector('.proxy-item');
proxyItem.dataset.id = config.id;
proxyItem.querySelector('.proxy-name').value = config.name;
proxyItem.querySelector('.proxy-type-select').value = config.proxyType;
updateProxyTypeSettings(proxyItem, config);
proxyList.appendChild(proxyItem);
});
}
function updateProxyTypeSettings(proxyItem, config) {
const serverSettings = proxyItem.querySelector('.proxy-server-settings');
const pacSettings = proxyItem.querySelector('.pac-script-settings');
if (config.proxyType === 'fixed_server') {
serverSettings.style.display = 'block';
pacSettings.style.display = 'none';
proxyItem.querySelector('.proxy-scheme').value = config.scheme || 'http';
proxyItem.querySelector('.proxy-host').value = config.host || '';
proxyItem.querySelector('.proxy-port').value = config.port || '';
}
else if (config.proxyType === 'pac_script') {
serverSettings.style.display = 'none';
pacSettings.style.display = 'block';
proxyItem.querySelector('.pac-script').value = config.pacScript || '';
}
else {
serverSettings.style.display = 'none';
pacSettings.style.display = 'none';
}
}
function setupEventListeners() {
// 添加代理按钮
document.getElementById('addProxy').addEventListener('click', addNewProxy);
// 代理列表变更事件
document.getElementById('proxyList').addEventListener('change', handleProxyChange);
// 删除代理按钮
document.getElementById('proxyList').addEventListener('click', handleProxyDelete);
}
async function addNewProxy() {
const newConfig = {
id: Date.now().toString(),
name: '新建代理',
proxyType: 'fixed_server',
scheme: 'http',
host: '127.0.0.1',
port: 8080
};
currentConfigs = [...currentConfigs, newConfig];
await ProxySettings.importSettings(currentConfigs);
renderProxyConfigs();
}
async function handleProxyChange(e) {
const proxyItem = e.target.closest('.proxy-item');
if (!proxyItem) return;
const configId = proxyItem.dataset.id;
const configIndex = currentConfigs.findIndex(c => c.id === configId);
if (configIndex === -1) return;
const updatedConfig = { ...currentConfigs[configIndex] };
if (e.target.classList.contains('proxy-name')) {
updatedConfig.name = e.target.value;
} else if (e.target.classList.contains('proxy-type-select')) {
updatedConfig.proxyType = e.target.value;
// 如果切换到固定服务器模式,设置默认值
if (updatedConfig.proxyType === 'fixed_server' && !updatedConfig.scheme) {
updatedConfig.scheme = 'http';
updatedConfig.host = '127.0.0.1';
updatedConfig.port = 8080;
}
} else if (e.target.classList.contains('proxy-scheme')) {
updatedConfig.scheme = e.target.value;
} else if (e.target.classList.contains('proxy-host')) {
updatedConfig.host = e.target.value;
} else if (e.target.classList.contains('proxy-port')) {
updatedConfig.port = parseInt(e.target.value) || '';
} else if (e.target.classList.contains('pac-script')) {
updatedConfig.pacScript = e.target.value;
}
currentConfigs[configIndex] = updatedConfig;
await ProxySettings.importSettings(currentConfigs);
// 如果代理类型改变,需要更新UI
if (e.target.classList.contains('proxy-type-select')) {
updateProxyTypeSettings(proxyItem, updatedConfig);
}
}
async function handleProxyDelete(e) {
const deleteBtn = e.target.closest('.delete-btn');
if (!deleteBtn) return;
const proxyItem = deleteBtn.closest('.proxy-item');
const configId = proxyItem.dataset.id;
currentConfigs = currentConfigs.filter(c => c.id !== configId);
await ProxySettings.importSettings(currentConfigs);
renderProxyConfigs();
}
function showError(message) {
const container = document.querySelector('.container');
const errorDiv = document.createElement('div');
errorDiv.className = 'error-message';
errorDiv.style.cssText = `
color: #ff4d4f;
padding: 8px 16px;
margin-bottom: 16px;
background-color: #fff2f0;
border: 1px solid #ffccc7;
border-radius: 4px;
`;
errorDiv.textContent = message;
container.insertBefore(errorDiv, container.firstChild);
// 5秒后自动移除错误信息
setTimeout(() => {
errorDiv.remove();
}, 5000);
}
// ... 其他代码保持不变
+98
View File
@@ -0,0 +1,98 @@
// 代理认证管理
export class ProxyAuth {
static async setupAuthListener() {
try {
// 保存认证信息到 storage
const saveAuth = async (config) => {
await chrome.storage.local.set({
proxyAuth: {
username: config.username,
password: config.password,
timestamp: Date.now()
}
});
};
// 获取认证信息
const getAuth = async () => {
const result = await chrome.storage.local.get('proxyAuth');
return result.proxyAuth;
};
// 清除认证信息
const clearAuth = async () => {
await chrome.storage.local.remove('proxyAuth');
};
return {
saveAuth,
getAuth,
clearAuth
};
} catch (error) {
console.error('Error setting up auth listener:', error);
return null;
}
}
static setupErrorHandler() {
// 在 Manifest V3 中,我们不能使用 chrome.proxy.onProxyError
// 所以我们只记录错误到 storage
try {
const logError = async (error) => {
const errors = await chrome.storage.local.get('proxyErrors') || [];
errors.push({
timestamp: Date.now(),
error: error.message || error
});
await chrome.storage.local.set({
proxyErrors: errors.slice(-100) // 只保留最近100条错误记录
});
};
return { logError };
} catch (error) {
console.error('Error setting up error handler:', error);
return null;
}
}
// 设置代理认证信息
static async setProxyAuth(username, password) {
try {
await chrome.storage.local.set({
proxyAuth: {
username,
password,
timestamp: Date.now()
}
});
return true;
} catch (error) {
console.error('Error setting proxy auth:', error);
return false;
}
}
// 获取代理认证信息
static async getProxyAuth() {
try {
const result = await chrome.storage.local.get('proxyAuth');
return result.proxyAuth || null;
} catch (error) {
console.error('Error getting proxy auth:', error);
return null;
}
}
// 清除代理认证信息
static async clearProxyAuth() {
try {
await chrome.storage.local.remove('proxyAuth');
return true;
} catch (error) {
console.error('Error clearing proxy auth:', error);
return false;
}
}
}
+87
View File
@@ -0,0 +1,87 @@
// 代理配置管理
export class ProxyManager {
static async setProxy(config) {
try {
const proxyConfig = {
mode: "fixed_servers",
rules: {
singleProxy: {
scheme: config.scheme,
host: config.host,
port: config.port
},
bypassList: ["localhost", "127.0.0.1"]
}
};
await chrome.proxy.settings.set({
value: proxyConfig,
scope: 'regular'
});
// 保存当前配置
await chrome.storage.local.set({
currentProxy: {
...config,
timestamp: Date.now()
}
});
return true;
} catch (error) {
console.error('Error setting proxy:', error);
return false;
}
}
static async clearProxy() {
try {
await chrome.proxy.settings.clear({scope: 'regular'});
await chrome.storage.local.remove('currentProxy');
return true;
} catch (error) {
console.error('Error clearing proxy:', error);
return false;
}
}
static async getProxyStatus() {
try {
const settings = await chrome.proxy.settings.get({});
const currentProxy = await chrome.storage.local.get('currentProxy');
return {
enabled: settings.value.mode === "fixed_servers",
config: currentProxy.currentProxy || null
};
} catch (error) {
console.error('Error getting proxy status:', error);
return {
enabled: false,
config: null
};
}
}
static async deleteProxy(proxyId) {
try {
// 获取当前代理配置
const result = await chrome.storage.local.get(['proxyConfigs', 'currentProxy']);
const configs = result.proxyConfigs || [];
const currentProxy = result.currentProxy;
// 如果要删除的代理正在使用中,先清除代理设置
if (currentProxy && currentProxy.id === proxyId) {
await clearProxyConfig();
}
// 从配置列表中删除代理
const updatedConfigs = configs.filter(config => config.id !== proxyId);
await chrome.storage.local.set({ proxyConfigs: updatedConfigs });
return true;
} catch (error) {
console.error('Error deleting proxy:', error);
return false;
}
}
}
+41
View File
@@ -0,0 +1,41 @@
// 代理配置存储和管理
export const ProxySettings = {
async importSettings(settings) {
try {
if (Array.isArray(settings) && settings.every(s => s.proxyType)) {
await chrome.storage.local.set({proxyConfigs: settings});
return {success: true};
}
return {success: false, error: "Invalid settings format"};
} catch (error) {
return {success: false, error: error.message};
}
},
async exportSettings() {
try {
const {proxyConfigs} = await chrome.storage.local.get('proxyConfigs');
return {success: true, settings: proxyConfigs || []};
} catch (error) {
return {success: false, error: error.message};
}
},
async setDefaultConfigs() {
const result = await chrome.storage.local.get('proxyConfigs');
if (!result.proxyConfigs) {
const defaultConfigs = [{
id: 'direct',
name: '直接连接',
proxyType: 'direct',
enabled: false
}];
await chrome.storage.local.set({ proxyConfigs: defaultConfigs });
}
// 确保 proxyLogs 存在
const logsResult = await chrome.storage.local.get('proxyLogs');
if (!logsResult.proxyLogs) {
await chrome.storage.local.set({ proxyLogs: [] });
}
}
};
+10
View File
@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<title>My Sidepanel</title>
</head>
<body>
<h1>All sites sidepanel extension</h1>
<p>This side panel is enabled on all sites</p>
</body>
</html>
+3 -2
View File
@@ -8,6 +8,7 @@ export const ActionType = {
CLEAR_PROXY: 'clear_proxy', CLEAR_PROXY: 'clear_proxy',
INJECT_SCRIPT: 'yakit_inject_script', INJECT_SCRIPT: 'yakit_inject_script',
TO_EXTENSION_PAGE: "yakit_to_extension_page", TO_EXTENSION_PAGE: "yakit_to_extension_page",
BADGE_COUNT: "yakit_badge",
} }
export class WebSocketManager { export class WebSocketManager {
@@ -22,7 +23,7 @@ export class WebSocketManager {
this.socket.onopen = () => { this.socket.onopen = () => {
chrome.runtime.sendMessage({action: ActionType.STATUS, connected: true, port: port}); chrome.runtime.sendMessage({action: ActionType.STATUS, connected: true, port: port});
// this.startHeartbeat(); this.startHeartbeat();
}; };
this.socket.onmessage = (event) => { this.socket.onmessage = (event) => {
@@ -66,7 +67,7 @@ export class WebSocketManager {
} }
startHeartbeat() { startHeartbeat() {
this.intervalId = setInterval(() => this.heartbeat(), 3000); this.intervalId = setInterval(() => this.heartbeat(), 25000);
} }
stopHeartbeat() { stopHeartbeat() {
+5
View File
@@ -0,0 +1,5 @@
export const ProxyActionType = {
SET_PROXY_CONFIG: "SET_PROXY_CONFIG",
CLEAR_PROXY_CONFIG: "CLEAR_PROXY_CONFIG",
GET_PROXY_STATUS: "GET_PROXY_STATUS"
};
+5 -3
View File
@@ -4,6 +4,7 @@ import {ConfigProvider} from "antd";
import {Contro} from "@components/Contro"; import {Contro} from "@components/Contro";
import {Proxifier} from "@components/Proxifier"; import {Proxifier} from "@components/Proxifier";
import {EvalInTab} from "@components/EvalInTab"; import {EvalInTab} from "@components/EvalInTab";
import {ProxySwitch} from "@components/ProxySwitch";
function App() { function App() {
return ( return (
@@ -15,10 +16,11 @@ function App() {
}} }}
> >
<div className="App"> <div className="App">
<Contro/> {/*<Contro/>*/}
{/*<Proxifier/>*/} {/* <Proxifier/> */}
<ProxySwitch/>
<EvalInTab/> {/* <EvalInTab/> */}
</div> </div>
</ConfigProvider> </ConfigProvider>
); );
+1
View File
@@ -32,6 +32,7 @@ export const EvalInTab: React.FC<EvalInTabProps> = () => {
value: {mode: "CONTENT_CALL_FUNCTION", fn_name: funcName, args: inputArgsData}, value: {mode: "CONTENT_CALL_FUNCTION", fn_name: funcName, args: inputArgsData},
}); });
} catch (error) { } catch (error) {
console.log("error:", error)
} }
}; };
+68
View File
@@ -0,0 +1,68 @@
.proxy-switch {
padding: 16px;
background: #fff;
border-radius: 4px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.proxy-switch-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.proxy-switch .ant-select {
margin-right: 8px;
}
.proxy-switch .ant-space {
gap: 8px;
}
.proxy-switch-title {
display: flex;
align-items: center;
}
.proxy-switch-title-text {
color: #31343F;
font-size: 14px;
font-weight: 600;
margin-right: 8px;
}
.proxy-switch-content {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
background: #F8F8F8;
border-radius: 4px;
}
.proxy-switch-info {
display: flex;
flex-direction: column;
gap: 4px;
}
.proxy-switch-status {
color: #85899E;
font-size: 12px;
}
.proxy-switch-proxy {
color: #31343F;
font-size: 13px;
}
.proxy-switch-status-active {
color: var(--yakit-primary);
}
.proxy-switch-actions {
display: flex;
align-items: center;
gap: 8px;
}
+288
View File
@@ -0,0 +1,288 @@
import React, {useEffect, useState} from "react";
import {Select, Button, Tooltip, Modal, Form, Input, Radio, Space, message} from "antd";
import {PlusOutlined, SettingOutlined} from "@ant-design/icons";
import {ProxyConfig} from "@/types/proxy";
import {StorageChanges} from "@/types/chrome";
import "./index.css";
export const ProxySwitch: React.FC = () => {
const [currentMode, setCurrentMode] = useState<string>("direct");
const [proxyConfigs, setProxyConfigs] = useState<ProxyConfig[]>([]);
const [isModalVisible, setIsModalVisible] = useState(false);
const [form] = Form.useForm();
const [proxyHost, setProxyHost] = useState('');
const [proxyPort, setProxyPort] = useState('');
useEffect(() => {
loadConfigs().catch(error => {
console.error("Failed to load configs:", error);
// 可以在这里添加错误提示
});
}, []);
useEffect(() => {
if (proxyConfigs.length > 0) {
applyProxySettings(currentMode).catch(error => {
console.error("Failed to apply proxy settings:", error);
// 可以在这里添加错误提示
});
}
}, [proxyConfigs, currentMode]);
useEffect(() => {
const handleStorageChange = (changes: StorageChanges) => {
if (changes.proxyConfigs) {
const newConfigs = changes.proxyConfigs.newValue;
setProxyConfigs(newConfigs);
const currentConfig = newConfigs.find((c: ProxyConfig) => c.id === currentMode);
if (!currentConfig || !currentConfig.enabled) {
setCurrentMode("direct");
}
}
};
chrome.storage.onChanged.addListener(handleStorageChange);
return () => chrome.storage.onChanged.removeListener(handleStorageChange);
}, [currentMode]);
const loadConfigs = async () => {
try {
const result = await chrome.storage.local.get('proxyConfigs');
if (!result.proxyConfigs) {
const defaultConfigs: ProxyConfig[] = [
{
id: "direct",
name: "直接连接",
proxyType: "direct" as const,
enabled: true
},
{
id: "default",
name: "默认代理",
proxyType: "fixed_server" as const,
host: "127.0.0.1",
port: 8080,
scheme: "http",
enabled: false
}
];
await chrome.storage.local.set({proxyConfigs: defaultConfigs});
setProxyConfigs(defaultConfigs);
setCurrentMode("direct");
} else {
setProxyConfigs(result.proxyConfigs);
const currentConfig = result.proxyConfigs.find((c: ProxyConfig) => c.id === currentMode);
if (!currentConfig || !currentConfig.enabled) {
setCurrentMode("direct");
}
}
return true;
} catch (error) {
console.error("Error in loadConfigs:", error);
throw error;
}
};
const applyProxySettings = async (modeId: string) => {
try {
const config = proxyConfigs.find((c: ProxyConfig) => c.id === modeId);
if (!config || !config.enabled) return false;
if (config.proxyType === "direct") {
chrome.proxy.settings.clear({scope: 'regular'});
return true;
} else if (config.proxyType === "fixed_server") {
chrome.proxy.settings.set({
value: {
mode: "fixed_servers",
rules: {
singleProxy: {
scheme: config.scheme,
host: config.host,
port: Number(config.port)
},
bypassList: ["<-loopback>"]
}
},
scope: "regular"
});
return true;
} else if (config.proxyType === "pac_script") {
chrome.proxy.settings.set({
value: {
mode: "pac_script",
pacScript: {
data: config.pacScript
}
},
scope: "regular"
});
return true;
}
return false;
} catch (error) {
console.error("Failed to apply proxy settings:", error);
throw error;
}
};
const handleAddProxy = () => {
form.resetFields();
setIsModalVisible(true);
};
const handleModalOk = () => {
form.validateFields().then(async values => {
try {
const newConfig: ProxyConfig = {
id: Date.now().toString(),
name: values.name,
proxyType: values.proxyType as "direct" | "fixed_server" | "pac_script" | "auto_detect",
host: values.host,
port: values.port ? parseInt(values.port) : undefined,
scheme: values.scheme,
enabled: true,
};
const updatedConfigs = [...proxyConfigs, newConfig];
await chrome.storage.local.set({proxyConfigs: updatedConfigs});
setProxyConfigs(updatedConfigs);
setIsModalVisible(false);
} catch (error) {
console.error("Failed to save new proxy config:", error);
// 可以在这里添加错误提示
}
});
};
const handleSettingClick = () => {
if (chrome.runtime.openOptionsPage) {
chrome.runtime.openOptionsPage();
} else {
chrome.tabs.create({
url: chrome.runtime.getURL('proxy/options.html')
});
}
};
const handleApplyProxy = async () => {
try {
const response = await chrome.runtime.sendMessage({
action: 'SET_PROXY_CONFIG',
config: {
host: proxyHost,
port: parseInt(proxyPort),
scheme: 'http',
proxyType: 'fixed_server',
id: 'default',
name: '默认代理'
}
});
if (response.success) {
message.success('代理设置已应用');
} else {
message.error(response.error || '代理设置失败');
}
} catch (error) {
console.error('Error applying proxy:', error);
message.error('应用代理设置时发生错误');
}
};
return (
<div className="proxy-switch">
<div className="proxy-switch-header">
<Select
value={currentMode}
onChange={setCurrentMode}
style={{width: 200}}
options={proxyConfigs.filter((config: ProxyConfig) => config.enabled).map((config: ProxyConfig) => ({
label: config.name,
value: config.id
}))}
/>
<Space>
<Tooltip title="添加代理">
<Button
icon={<PlusOutlined/>}
onClick={handleAddProxy}
/>
</Tooltip>
<Tooltip title="代理设置">
<Button
icon={<SettingOutlined/>}
onClick={handleSettingClick}
/>
</Tooltip>
</Space>
</div>
<Modal
title="添加代理配置"
open={isModalVisible}
onOk={handleModalOk}
onCancel={() => setIsModalVisible(false)}
>
<Form
form={form}
layout="vertical"
>
<Form.Item
name="name"
label="配置名称"
rules={[{required: true}]}
>
<Input/>
</Form.Item>
<Form.Item
name="proxyType"
label="代理类型"
rules={[{required: true}]}
>
<Radio.Group>
<Radio value="direct"></Radio>
<Radio value="fixed_server"></Radio>
<Radio value="pac_script">PAC </Radio>
</Radio.Group>
</Form.Item>
<Form.Item
noStyle
shouldUpdate={(prev, curr) => prev.proxyType !== curr.proxyType}
>
{({getFieldValue}) =>
getFieldValue('proxyType') === 'fixed_server' && (
<>
<Form.Item
name="scheme"
label="代理协议"
rules={[{required: true}]}
>
<Select>
<Select.Option value="http">HTTP</Select.Option>
<Select.Option value="https">HTTPS</Select.Option>
<Select.Option value="socks4">SOCKS4</Select.Option>
<Select.Option value="socks5">SOCKS5</Select.Option>
</Select>
</Form.Item>
<Form.Item
name="host"
label="代理服务器"
rules={[{required: true}]}
>
<Input/>
</Form.Item>
<Form.Item
name="port"
label="端口"
rules={[{required: true}]}
>
<Input type="number"/>
</Form.Item>
</>
)
}
</Form.Item>
</Form>
</Modal>
</div>
);
};
+100
View File
@@ -0,0 +1,100 @@
.options-page {
min-height: 100vh;
background: #f0f2f5;
}
.options-page .ant-layout-content {
padding: 24px;
}
.proxy-tabs {
background: #fff;
padding: 24px;
border-radius: 8px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
.options-page .ant-tabs-nav::before {
border-bottom-color: #f0f0f0;
}
.options-page .ant-tabs-tab.ant-tabs-tab-active .ant-tabs-tab-btn {
color: var(--yakit-primary);
}
.options-page .ant-tabs-ink-bar {
background: var(--yakit-primary);
}
.options-page .ant-card {
margin-bottom: 16px;
border-radius: 4px;
border: 1px solid #f0f0f0;
}
.options-page .ant-card-head {
border-bottom: none;
min-height: 48px;
}
.options-page .ant-card-body {
padding: 16px;
}
.options-page .ant-btn-primary {
background: var(--yakit-primary);
border-color: var(--yakit-primary);
}
.options-page .ant-btn-primary:hover {
background: var(--yakit-primary-hover);
border-color: var(--yakit-primary-hover);
}
.proxy-action-btn.ant-btn-default {
color: rgba(0, 0, 0, 0.65);
border-color: #d9d9d9;
background: #fff;
}
.proxy-action-btn.ant-btn-default:hover {
color: var(--yakit-primary);
border-color: var(--yakit-primary);
}
.proxy-action-btn.ant-btn-primary {
color: #fff;
background: var(--yakit-primary);
border-color: var(--yakit-primary);
}
.proxy-action-btn.ant-btn-primary.ant-btn-dangerous {
background: #ff4d4f;
border-color: #ff4d4f;
}
.proxy-action-btn.ant-btn-primary.ant-btn-dangerous:hover {
background: #ff7875;
border-color: #ff7875;
}
.options-page .ant-table-wrapper {
background: #fff;
padding: 24px;
border-radius: 8px;
}
.options-page .ant-input:focus,
.options-page .ant-input-focused {
border-color: var(--yakit-primary);
box-shadow: 0 0 0 2px var(--yakit-primary-5);
}
.options-page .ant-select-focused .ant-select-selector {
border-color: var(--yakit-primary) !important;
box-shadow: 0 0 0 2px var(--yakit-primary-5) !important;
}
.options-page .ant-select-item-option-selected:not(.ant-select-item-option-disabled) {
background-color: var(--yakit-primary-5);
}
+385
View File
@@ -0,0 +1,385 @@
import React, { useEffect, useState } from "react";
import { Layout, Button, Card, Input, Select, InputNumber, Space, Typography, Modal, Switch, Tabs, Table, Form, App } from "antd";
import { PlusOutlined, DeleteOutlined, ImportOutlined, ExportOutlined } from "@ant-design/icons";
import { ProxyConfig } from "@/types/proxy";
import { StorageChanges } from "@/types/chrome";
import './index.css';
import { ProxyActionType } from '@/types/action';
const { Header, Content } = Layout;
const { Title } = Typography;
const { TextArea } = Input;
const headerStyle = {
background: '#fff',
padding: '0 24px',
borderBottom: '1px solid #f0f0f0'
};
const contentStyle = {
padding: '24px',
background: '#f0f2f5',
minHeight: '100vh'
};
const titleStyle = {
margin: '16px 0',
color: '#31343F'
};
interface ProxyLog {
id: string;
timestamp: number;
url: string;
proxyId: string;
proxyName: string;
status: 'success' | 'error';
errorMessage?: string;
}
export const OptionsPage: React.FC = () => {
const { message } = App.useApp();
const [proxyConfigs, setProxyConfigs] = useState<ProxyConfig[]>([]);
const [proxyLogs, setProxyLogs] = useState<ProxyLog[]>([]);
const [activeTab, setActiveTab] = useState('settings');
const [currentConfigId, setCurrentConfigId] = useState<string>('');
useEffect(() => {
loadConfigs();
}, []);
const loadConfigs = async () => {
const result = await chrome.storage.local.get('proxyConfigs');
setProxyConfigs(result.proxyConfigs || []);
};
const handleAddProxy = () => {
const newConfig: ProxyConfig = {
id: Date.now().toString(),
name: '新建代理',
proxyType: 'fixed_server',
scheme: 'http',
host: '127.0.0.1',
port: 8080,
enabled: false
};
const updatedConfigs = [...proxyConfigs, newConfig];
chrome.storage.local.set({ proxyConfigs: updatedConfigs });
setProxyConfigs(updatedConfigs);
};
const handleConfigChange = (configId: string, field: keyof ProxyConfig, value: any) => {
const updatedConfigs = proxyConfigs.map(config => {
if (config.id === configId) {
return { ...config, [field]: value };
}
return config;
});
chrome.storage.local.set({ proxyConfigs: updatedConfigs });
setProxyConfigs(updatedConfigs);
};
const handleDeleteProxy = (configId: string) => {
Modal.confirm({
title: '确认删除',
content: '确定要删除这个代理配置吗?',
onOk: () => {
const updatedConfigs = proxyConfigs.filter(config => config.id !== configId);
chrome.storage.local.set({ proxyConfigs: updatedConfigs });
setProxyConfigs(updatedConfigs);
}
});
};
useEffect(() => {
const handleStorageChange = (changes: StorageChanges) => {
if (changes.proxyConfigs) {
setProxyConfigs(changes.proxyConfigs.newValue || []);
}
};
chrome.storage.onChanged.addListener(handleStorageChange);
return () => chrome.storage.onChanged.removeListener(handleStorageChange);
}, []);
useEffect(() => {
const loadLogs = async () => {
const result = await chrome.storage.local.get('proxyLogs');
setProxyLogs(result.proxyLogs || []);
};
loadLogs();
const handleStorageChange = (changes: StorageChanges) => {
if (changes.proxyLogs) {
setProxyLogs(changes.proxyLogs.newValue);
}
};
chrome.storage.onChanged.addListener(handleStorageChange);
return () => chrome.storage.onChanged.removeListener(handleStorageChange);
}, []);
const columns = [
{
title: '时间',
dataIndex: 'timestamp',
key: 'timestamp',
render: (timestamp: number) => new Date(timestamp).toLocaleString()
},
{
title: 'URL',
dataIndex: 'url',
key: 'url',
ellipsis: true,
render: (url: string) => (
<a
href={url}
target="_blank"
rel="noopener noreferrer"
style={{
color: '#1890ff',
textDecoration: 'none',
maxWidth: '400px',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
display: 'block'
}}
onClick={(e) => {
e.preventDefault();
chrome.tabs.create({ url });
}}
>
{url}
</a>
)
},
{
title: '使用代理',
dataIndex: 'proxyName',
key: 'proxyName',
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
render: (status: string) => (
<span style={{ color: status === 'success' ? '#52c41a' : '#ff4d4f' }}>
{status === 'success' ? '成功' : '失败'}
</span>
)
},
{
title: '错误信息',
dataIndex: 'errorMessage',
key: 'errorMessage',
ellipsis: true,
}
];
const handleApplyConfig = async (configId: string) => {
const config = proxyConfigs.find(c => c.id === configId);
if (config) {
try {
const response = await new Promise<any>((resolve) => {
chrome.runtime.sendMessage({
action: ProxyActionType.SET_PROXY_CONFIG,
config: {
...config,
scheme: config.scheme || 'http',
host: config.host || '127.0.0.1',
port: Number(config.port) || 8080,
}
}, resolve);
});
if (response && response.success) {
const updatedConfigs = proxyConfigs.map(c => ({
...c,
enabled: c.id === configId
}));
await chrome.storage.local.set({ proxyConfigs: updatedConfigs });
setProxyConfigs(updatedConfigs);
message.success('代理设置已应用');
} else {
message.error((response && response.error) || '代理设置失败');
}
} catch (error) {
console.error('Failed to apply proxy config:', error);
message.error('操作失败');
}
}
};
const handleClearProxy = async (configId: string) => {
try {
const response = await new Promise<any>((resolve) => {
chrome.runtime.sendMessage({
action: ProxyActionType.CLEAR_PROXY_CONFIG
}, resolve);
});
if (response && response.success) {
const updatedConfigs = proxyConfigs.map(c => ({
...c,
enabled: false
}));
await chrome.storage.local.set({ proxyConfigs: updatedConfigs });
setProxyConfigs(updatedConfigs);
message.success('代理已取消');
} else {
message.error((response && response.error) || '取消代理失败');
}
} catch (error) {
console.error('Error clearing proxy:', error);
message.error('操作失败');
}
};
return (
<Layout className="options-page">
<Content style={contentStyle}>
<Tabs
activeKey={activeTab}
onChange={setActiveTab}
className="proxy-tabs"
tabBarExtraContent={{
right: (
<Space>
<Button
type="primary"
icon={<PlusOutlined />}
onClick={handleAddProxy}
>
</Button>
<Button icon={<ImportOutlined />}></Button>
<Button icon={<ExportOutlined />}></Button>
</Space>
)
}}
items={[
{
key: 'settings',
label: '代理设置',
children: (
<Space direction="vertical" style={{ width: '100%' }}>
{proxyConfigs.map(config => (
<Card
key={config.id}
size="small"
title={
<Input
placeholder="代理名称"
value={config.name}
onChange={e => handleConfigChange(config.id, 'name', e.target.value)}
disabled={config.id === 'direct'}
variant="borderless"
style={{ fontSize: '16px', padding: 0 }}
/>
}
extra={
<Space>
<Button
className="proxy-action-btn"
type={config.enabled ? "primary" : "default"}
danger={config.enabled}
onClick={() => config.enabled ?
handleClearProxy(config.id) :
handleApplyConfig(config.id)
}
>
{config.enabled ? '取消应用' : '应用选项'}
</Button>
{config.id !== 'direct' && (
<Button
danger
icon={<DeleteOutlined />}
onClick={() => handleDeleteProxy(config.id)}
/>
)}
</Space>
}
style={{ borderRadius: '4px' }}
>
<Space direction="vertical" style={{ width: '100%' }}>
<Select
style={{ width: '100%' }}
value={config.proxyType}
onChange={value => handleConfigChange(config.id, 'proxyType', value)}
disabled={config.id === 'direct'}
>
<Select.Option value="direct"></Select.Option>
<Select.Option value="fixed_server"></Select.Option>
<Select.Option value="pac_script">PAC </Select.Option>
<Select.Option value="bypass_list"></Select.Option>
</Select>
{config.proxyType === 'bypass_list' && (
<TextArea
rows={4}
value={config.bypassList?.join('\n')}
onChange={e => handleConfigChange(config.id, 'bypassList', e.target.value.split('\n'))}
placeholder="每行一个规则,例如:
*.example.com
[::1]
127.0.0.1"
/>
)}
{config.proxyType === 'fixed_server' && (
<Space style={{ width: '100%' }}>
<Select
style={{ width: 120 }}
value={config.scheme}
onChange={value => handleConfigChange(config.id, 'scheme', value)}
>
<Select.Option value="http">HTTP</Select.Option>
<Select.Option value="https">HTTPS</Select.Option>
<Select.Option value="socks4">SOCKS4</Select.Option>
<Select.Option value="socks5">SOCKS5</Select.Option>
</Select>
<Input
placeholder="代理服务器"
value={config.host}
onChange={e => handleConfigChange(config.id, 'host', e.target.value)}
/>
<InputNumber
placeholder="端口"
value={config.port}
onChange={value => handleConfigChange(config.id, 'port', value)}
style={{ width: 100 }}
/>
</Space>
)}
{config.proxyType === 'pac_script' && (
<TextArea
rows={4}
value={config.pacScript}
onChange={e => handleConfigChange(config.id, 'pacScript', e.target.value)}
placeholder="输入 PAC 脚本"
/>
)}
</Space>
</Card>
))}
</Space>
)
},
{
key: 'logs',
label: '代理日志',
children: (
<Table
dataSource={proxyLogs}
columns={columns}
pagination={{ pageSize: 50 }}
/>
)
}
]}
/>
</Content>
</Layout>
);
};
+15
View File
@@ -0,0 +1,15 @@
import React from 'react';
import { createRoot } from 'react-dom/client';
import { App } from 'antd';
import { OptionsPage } from './OptionsPage';
import zhCN from 'antd/locale/zh_CN';
import '@/styles/global.css';
const container = document.getElementById('root');
const root = createRoot(container!);
root.render(
<App>
<OptionsPage />
</App>
);
+37
View File
@@ -0,0 +1,37 @@
:root {
--yakit-primary: #F28B44;
--yakit-primary-hover: #f4a061;
--yakit-primary-active: #e87633;
--yakit-primary-5: rgba(242, 139, 68, 0.05);
--yakit-primary-10: rgba(242, 139, 68, 0.1);
}
.ant-btn-primary {
background-color: var(--yakit-primary) !important;
}
.ant-btn-primary:hover {
background-color: var(--yakit-primary-hover) !important;
}
.ant-btn-primary:active {
background-color: var(--yakit-primary-active) !important;
}
.ant-select-item-option-selected:not(.ant-select-item-option-disabled) {
background-color: var(--yakit-primary-5) !important;
}
.ant-select-focused .ant-select-selector,
.ant-input-focused,
.ant-input:focus,
.ant-input-number-focused,
.ant-input-number:focus {
border-color: var(--yakit-primary) !important;
box-shadow: 0 0 0 2px var(--yakit-primary-10) !important;
}
.ant-btn:not(.ant-btn-primary):hover {
color: var(--yakit-primary) !important;
border-color: var(--yakit-primary) !important;
}
+15
View File
@@ -0,0 +1,15 @@
export const ProxyActionType = {
CONNECT: "CONNECT",
SEND_MESSAGE: "SEND_MESSAGE",
DISCONNECT: "DISCONNECT",
SET_PROXY: "SET_PROXY",
CLEAR_PROXY: "CLEAR_PROXY",
PROXY_STATUS: "PROXY_STATUS",
INJECT_SCRIPT: "INJECT_SCRIPT",
BADGE_COUNT: "BADGE_COUNT",
SET_PROXY_CONFIG: "SET_PROXY_CONFIG",
CLEAR_PROXY_CONFIG: "CLEAR_PROXY_CONFIG",
GET_PROXY_STATUS: "GET_PROXY_STATUS"
} as const;
export type ProxyActionType = typeof ProxyActionType[keyof typeof ProxyActionType];
+19
View File
@@ -0,0 +1,19 @@
export interface StorageChange<T = any> {
oldValue?: T;
newValue?: T;
}
export interface StorageChanges {
[key: string]: StorageChange;
}
export interface ProxyConfig {
host: string;
port: number;
scheme: 'http' | 'https' | 'socks5';
proxyType: 'fixed_server';
enabled?: boolean;
id?: string;
name?: string;
timestamp?: number;
}
+11
View File
@@ -0,0 +1,11 @@
export interface ProxyConfig {
id: string;
name: string;
enabled: boolean;
proxyType: "direct" | "fixed_server" | "pac_script" | "auto_detect" | "bypass_list";
host?: string;
port?: number;
scheme?: "http" | "https" | "socks4" | "socks5";
pacScript?: string;
bypassList?: string[];
}
+5 -1
View File
@@ -10,7 +10,11 @@
"moduleResolution": "node", "moduleResolution": "node",
"baseUrl": "./", "baseUrl": "./",
"paths": { "paths": {
"@*": ["src/*"] "@/*": ["./src/*"],
"@components/*": ["./src/components/*"],
"@assets/*": ["./src/assets/*"],
"@network/*": ["./src/network/*"],
"@types/*": ["./src/types/*"]
} }
}, },
"include": [ "include": [
+20 -7
View File
@@ -6,10 +6,15 @@ const webpack = require('webpack');
module.exports = { module.exports = {
mode: 'development', // 设置模式为开发模式 mode: 'development', // 设置模式为开发模式
entry: './src/index.jsx', // 指定入口文件 entry: {
main: './src/index.jsx',
options: './src/pages/options.tsx'
},
output: { output: {
path: path.resolve(__dirname, 'build'), // 输出目录 path: path.resolve(__dirname, 'build'), // 输出目录
filename: 'bundle.js', // 输出文件名 filename: '[name].bundle.js', // 输出文件名
publicPath: '/',
clean: true
}, },
plugins: [ plugins: [
new webpack.DefinePlugin({ new webpack.DefinePlugin({
@@ -18,16 +23,22 @@ module.exports = {
}), }),
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
template: './public/index.html', template: './public/index.html',
filename: 'index.html' filename: 'index.html',
chunks: ['main']
}),
new HtmlWebpackPlugin({
template: './public/proxy/options.html',
filename: 'proxy/options.html',
chunks: ['options'],
publicPath: '../'
}), }),
new CopyWebpackPlugin({ new CopyWebpackPlugin({
patterns: [ patterns: [
// copy public assets exclude index.html
{ {
from: path.resolve(__dirname, 'public'), from: path.resolve(__dirname, 'public'),
to: path.resolve(__dirname, 'build'), to: path.resolve(__dirname, 'build'),
globOptions: { globOptions: {
ignore: ['**/index.html'] ignore: ['**/index.html', '**/proxy/options.html']
} }
} }
] ]
@@ -37,8 +48,8 @@ module.exports = {
module: { module: {
rules: [ rules: [
{ {
test: /\.css$/, // 匹配所有的 css 文件 test: /\.css$/,
use: ['style-loader', 'css-loader'] // 对匹配到的文件使用这两个 loader use: ['style-loader', 'css-loader']
}, },
{ {
test: /\.tsx?$/, // 匹配TS和TSX文件 test: /\.tsx?$/, // 匹配TS和TSX文件
@@ -60,9 +71,11 @@ module.exports = {
resolve: { resolve: {
extensions: ['.tsx', '.ts', '.js', '.jsx'], // 解析扩展(确保能够解析JS和JSX文件) extensions: ['.tsx', '.ts', '.js', '.jsx'], // 解析扩展(确保能够解析JS和JSX文件)
alias: { alias: {
'@': path.resolve(__dirname, './src'),
'@assets': path.resolve(__dirname, './src/assets'), '@assets': path.resolve(__dirname, './src/assets'),
'@components': path.resolve(__dirname, './src/components'), '@components': path.resolve(__dirname, './src/components'),
'@network': path.resolve(__dirname, './src/network'), '@network': path.resolve(__dirname, './src/network'),
'@types': path.resolve(__dirname, './src/types'),
} }
}, },
devtool: 'inline-source-map', // 生成内联源映射,便于调试 devtool: 'inline-source-map', // 生成内联源映射,便于调试
+10661
View File
File diff suppressed because it is too large Load Diff