Compare commits

...
Author SHA1 Message Date
go0p 0b8d8c81b9 format 2024-05-15 14:35:35 +08:00
go0p dd1b2a9513 eval done && fix findDOMNode warning 2024-05-14 23:02:40 +08:00
go0p cb65d57cef 完善一下 2024-05-14 21:49:22 +08:00
song_xiao_lin 68ae019c7d del: 删除无用代码 2024-05-14 16:42:11 +08:00
song_xiao_lin 65faff9e57 del: 删除无用代码 2024-05-14 16:19:04 +08:00
song_xiao_lin 01f04e3e56 fix: 注入js 2024-05-14 15:53:15 +08:00
song_xiao_lin ed1005936b fix: 重复监听 2024-05-14 11:47:21 +08:00
go0p fffbb2a0a0 调用active tab js func 2024-05-13 15:01:38 +08:00
go0p 95bb29d216 fix 2024-05-12 22:35:50 +08:00
go0p 7e1e9e2c79 eval in page context 2024-05-12 20:30:19 +08:00
song_xiao_lin f881ad6ce2 pref 2024-05-09 15:06:21 +08:00
song_xiao_lin 0090c60141 feat: 删掉提示 2024-05-09 14:54:37 +08:00
song_xiao_lin f2d360fae0 fix: 状态问题 2024-05-09 14:52:07 +08:00
song_xiao_lin 514006fef5 fix: 失败重连问题 2024-05-08 18:29:58 +08:00
song_xiao_lin eaecc8eacd fix: 断开连接 2024-05-08 17:06:57 +08:00
song_xiao_lin 4daf96f637 fix: 部分问题 2024-05-08 16:33:27 +08:00
song_xiao_lin 98e585680a fix: 修复重连问题 2024-05-08 15:53:46 +08:00
go0p 5ab87eec74 inject script 2024-05-08 14:52:24 +08:00
go0p c6234ae79c inject script 2024-05-08 14:48:02 +08:00
go0p fbe662c056 add inject code 2024-05-07 22:53:11 +08:00
go0p 5424078d38 feat(wsc): fix 2024-05-07 20:44:29 +08:00
song_xiao_lin 611d9543fe zancun 2024-04-23 14:10:54 +08:00
song_xiao_lin c763e28a34 feat: Contro逻辑调整 2024-04-23 12:47:07 +08:00
song_xiao_lin 9b0c89f1b1 style: icon点击样式给主题色 2024-04-22 18:00:24 +08:00
song_xiao_lin 3ab46d55c0 feat: Contro 2024-04-22 17:35:43 +08:00
song_xiao_lin 19015fae87 feat: 代理功能 2024-04-22 11:06:16 +08:00
song_xiao_lin 4cba932506 style: 样式调整 2024-04-16 16:24:48 +08:00
23 changed files with 1480 additions and 14566 deletions
+3
View File
@@ -310,6 +310,9 @@ module.exports = function (webpackEnv) {
.map(ext => `.${ext}`)
.filter(ext => useTypeScript || !ext.includes('ts')),
alias: {
'@assets': path.resolve(__dirname, '../src/assets'),
'@components': path.resolve(__dirname, '../src/components'),
'@network': path.resolve(__dirname, '../src/network'),
// Support React Native Web
// https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
'react-native': 'react-native-web',
+135 -14137
View File
File diff suppressed because it is too large Load Diff
+3 -1
View File
@@ -9,7 +9,7 @@
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"ahooks": "^3.7.10",
"antd": "^5.15.3",
"antd": "^5.17.2",
"babel-jest": "^27.4.2",
"babel-plugin-named-asset-import": "^0.3.8",
"babel-preset-react-app": "^10.0.1",
@@ -17,6 +17,7 @@
"browserslist": "^4.18.1",
"camelcase": "^6.2.1",
"case-sensitive-paths-webpack-plugin": "^2.4.0",
"classnames": "^2.5.1",
"css-minimizer-webpack-plugin": "^3.2.0",
"dotenv": "^10.0.0",
"dotenv-expand": "^5.1.0",
@@ -137,6 +138,7 @@
"@babel/core": "^7.24.1",
"@babel/preset-env": "^7.24.1",
"@babel/preset-react": "^7.24.1",
"@types/chrome": "^0.0.268",
"babel-loader": "^9.1.3",
"copy-webpack-plugin": "^12.0.2",
"cross-env": "^7.0.3",
+78 -75
View File
@@ -1,85 +1,88 @@
let socket;
const connectWebsocket = url => {
disconnectWebsocket()
socket = new WebSocket(url);
socket.onopen = () => {
chrome.runtime.sendMessage({status: "connected"})
}
socket.onclose = () => {
chrome.runtime.sendMessage({status: "disconnected"})
}
}
import {ActionType, WebSocketManager} from './socket.js';
const disconnectWebsocket = () => {
if (socket) {
try {
socket.close()
socket = null;
} catch (e) {
}
}
}
heartbeat = () => {
if (socket) {
if (socket.readyState === WebSocket.OPEN) {
try {
socket.send(JSON.stringify({
"type": "heartbeat",
}))
} catch (e) {
console.error("Error sending heartbeat:", e);
}
} else {
disconnectWebsocket()
}
}
}
heartbeat()
setInterval(heartbeat, 3000)
console.info("Chrome Extenstion Background is loaded")
let proxyHost = "";
const websocketManager = new WebSocketManager();
chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) {
if (msg.action === "connect") {
console.info("Start to connect websocket")
const host = msg['host'] || "127.0.0.1"
const port = msg['port'] || 11212
connectWebsocket(`ws://${host}:${port}/?token=${"a"}`)
} else if (msg.action === 'disconnect') {
disconnectWebsocket()
} else if (msg.action === "status") {
if (socket) {
chrome.runtime.sendMessage({connected: true})
} else {
chrome.runtime.sendMessage({connected: false})
}
} else if (msg.action === 'setproxy') {
chrome.proxy.settings.set({
value: {
mode: "fixed_servers",
rules: {
singleProxy: {
scheme: msg.scheme,
host: msg.host,
port: parseInt(`${msg.port}`)
console.log("msg", msg)
switch (msg.action) {
case ActionType.CONNECT:
console.info("Start to connect websocket")
const host = msg['host'] || "127.0.0.1"
const port = msg['port'] || 11212
websocketManager.connectWebsocket(`ws://${host}:${port}/?token=${"a"}`, port)
break;
case ActionType.DISCONNECT:
websocketManager.disconnectWebsocket();
break;
case ActionType.SET_PROXY:
chrome.proxy.settings.set({
value: {
mode: "fixed_servers",
rules: {
singleProxy: {
scheme: msg.scheme,
host: msg.host,
port: parseInt(`${msg.port}`)
}
}
},
scope: 'regular',
});
break;
case ActionType.CLEAR_PROXY:
chrome.proxy.settings.clear({})
break;
case ActionType.PROXY_STATUS:
chrome.proxy.settings.get({}, function (details) {
if (details.value && details.value.mode === "fixed_servers") {
let proxyConfig = details.value.rules.singleProxy;
chrome.runtime.sendMessage({
enable: true,
proxy: `${proxyConfig.scheme}://${proxyConfig.host}:${proxyConfig.port}`
})
} else {
chrome.runtime.sendMessage({enable: false, proxy: ""})
}
},
scope: 'regular',
})
proxyHost = `${msg.scheme}://${msg.host}:${msg.port}`
} else if (msg.action === 'clearproxy') {
chrome.proxy.settings.clear({})
proxyHost = ""
} else if (msg.action === 'proxystatus') {
if (proxyHost !== '') {
chrome.runtime.sendMessage({enable: !!proxyHost, proxy: proxyHost})
} else {
chrome.runtime.sendMessage({enable: false, proxy: ""})
}
});
break;
case ActionType.INJECT_SCRIPT:
(async () => {
try {
// 注入 JS 脚本
await chrome.scripting.executeScript({
target: {tabId: msg.tabId},
files: ['content.js']
});
// 发送消息
const response = await chrome.tabs.sendMessage(msg.tabId, {
type: ActionType.INJECT_SCRIPT,
value: msg.value
});
console.log("response", response);
if (response && response.action === ActionType.TO_EXTENSION_PAGE) {
await chrome.runtime.sendMessage(response);
}
} catch (err) {
console.error('Script or CSS injection failed:', err);
}
})();
break
}
})
const pageFunction = (code) => {
chrome.runtime.sendMessage({code}, response => {
if (response && response.success) {
console.log('Result:', response.result);
} else {
console.error('Error:', response.error);
}
});
}
+55
View File
@@ -0,0 +1,55 @@
(() => {
if (window.contentScriptInjected) {
return;
}
window.contentScriptInjected = true;
// 检查并插入 CSS 样式
const styleId = 'injected-css-style';
if (!document.getElementById(styleId)) {
const style = document.createElement('style');
style.id = styleId;
style.textContent = `
body {
border: 3px solid red;
position: relative; /* Ensure the body is positioned to allow the pseudo-element */
}
body::after {
content: "Injection successful";
display: block;
position: fixed;
top: 10px;
right: 10px;
background: green;
color: white;
padding: 5px 10px;
font-size: 16px;
z-index: 1000;
}
`;
document.head.appendChild(style);
}
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.type === 'yakit_inject_script') {
const injectedScriptURL = chrome.runtime.getURL('inject.js');
const script = document.createElement('script');
script.src = injectedScriptURL;
script.onload = () => {
window.postMessage({type: request.value.mode, value: request.value}, '*');
script.remove();
};
(document.head || document.documentElement).appendChild(script);
window.addEventListener('message', function onMessage(event) {
if (event.source !== window || event.data.type !== 'FROM_INJECT_JS') {
return;
}
window.removeEventListener('message', onMessage);
// Send the result to the background script
// chrome.runtime.sendMessage({ action: 'yakit_to_extension_page', result: event.data.result });
// 直接向向发送端返回结果
sendResponse({action: 'yakit_to_extension_page', result: event.data.result});
});
return true;
}
});
})()
-1
View File
@@ -7,7 +7,6 @@
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>
+37
View File
@@ -0,0 +1,37 @@
(() => {
if (window.injectedMessageListener) {
return;
}
window.injectedMessageListener = true;
window.addEventListener('message', function onMessage(event) {
if (event.source !== window) {
return;
}
let result
switch (event.data.type) {
case 'CONTENT_CALL_FUNCTION':
const fn_name = event.data.value.fn_name;
const args = event.data.value.args;
result = window[fn_name](args);
window.postMessage({type: 'FROM_INJECT_JS', result: result}, '*');
break;
case 'CONTENT_EVAL_CODE':
const code = event.data.value.code;
result = (() => {
try {
return eval(code);
} catch (e) {
// console.error("Error evaluating code:", e);
return e.toString();
}
})();
// console.log("CONTENT_EVAL_CODE result: ", result);
window.postMessage({type: 'FROM_INJECT_JS', result: result}, '*');
break;
default:
break;
}
});
})();
+11 -8
View File
@@ -12,9 +12,11 @@
}
},
"background": {
"service_worker": "background.js"
"service_worker": "background.js",
"type": "module"
},
"permissions": [
"webNavigation",
"activeTab",
"scripting",
"tabs",
@@ -22,14 +24,15 @@
"storage",
"webRequest"
],
"content_scripts": [
"host_permissions": [
"<all_urls>"
],
"web_accessible_resources": [
{
"matches": [
"<all_urls>"
],
"js": [
"content.js"
]
"resources": ["inject.js"],
"matches": ["<all_urls>"],
"use_dynamic_url": true
}
],
"icons": {
+83
View File
@@ -0,0 +1,83 @@
export const ActionType = {
CONNECT: 'connect',
DISCONNECT: 'disconnect',
STATUS: 'status',
PROXY_STATUS: 'proxy_status',
SET_PROXY: 'set_proxy',
CLEAR_PROXY: 'clear_proxy',
INJECT_SCRIPT: 'yakit_inject_script',
TO_EXTENSION_PAGE: "yakit_to_extension_page",
}
export class WebSocketManager {
constructor() {
this.socket = null;
this.intervalId = null;
}
connectWebsocket(url, port) {
this.disconnectWebsocket();
this.socket = new WebSocket(url);
this.socket.onopen = () => {
chrome.runtime.sendMessage({action: ActionType.STATUS, connected: true, port: port});
this.startHeartbeat();
};
this.socket.onmessage = (event) => {
this.handleMessage(event.data);
};
this.socket.onclose = () => {
chrome.runtime.sendMessage({action: ActionType.STATUS, connected: false});
};
this.socket.onerror = (error) => {
console.error("WebSocket Error:", error);
};
}
disconnectWebsocket() {
if (this.socket) {
try {
this.socket.close();
chrome.runtime.sendMessage({action: ActionType.STATUS, connected: false});
} catch (e) {
console.error("Error closing websocket:", e);
}
this.socket = null;
this.stopHeartbeat();
}
}
startHeartbeat() {
this.intervalId = setInterval(() => this.heartbeat(), 3000);
}
stopHeartbeat() {
if (this.intervalId) {
clearInterval(this.intervalId);
this.intervalId = null;
}
}
heartbeat() {
if (this.isConnected()) {
try {
this.socket.send(JSON.stringify({"type": "heartbeat"}));
} catch (e) {
console.error("Error sending heartbeat:", e);
}
} else {
this.disconnectWebsocket();
}
}
isConnected() {
return this.socket && this.socket.readyState === WebSocket.OPEN;
}
handleMessage(message) {
console.log("message", message)
}
}
+8 -2
View File
@@ -1,5 +1,10 @@
.App {
text-align: center;
width: 420px;
border-radius: 0px 0px 4px 4px;
border-right: 1px solid #EAECF3;
border-bottom: 1px solid #EAECF3;
border-left: 1px solid #EAECF3;
background: #fff;
}
.App-logo {
@@ -32,7 +37,8 @@
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
}
+20 -15
View File
@@ -1,21 +1,26 @@
import React from 'react';
import './App.css';
import {Layout, Row} from 'antd';
import {Controller} from "./components/Controller";
import {Proxifier} from "./components/Proxifier";
import React from "react";
import "./App.css";
import {ConfigProvider} from "antd";
import {Contro} from "@components/Contro";
import {Proxifier} from "@components/Proxifier";
import {EvalInTab} from "@components/EvalInTab";
function App() {
return (
<div className="App" style={{width: 300, backgroundColor: "#eee", padding: 8}}>
<Layout>
<Row>
<Controller/>
</Row>
<Row>
<Proxifier/>
</Row>
</Layout>
</div>
<ConfigProvider
theme={{
token: {
colorPrimary: "#F28B44",
},
}}
>
<div className="App">
{/*<Contro/>*/}
<Proxifier/>
<EvalInTab/>
</div>
</ConfigProvider>
);
}
+168
View File
@@ -0,0 +1,168 @@
import Icon from "@ant-design/icons";
import { CustomIconComponentProps } from "@ant-design/icons/lib/components/Icon";
import React from "react";
interface IconProps extends CustomIconComponentProps {
onClick: (e: React.MouseEvent) => void;
ref?: any;
}
const X = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
>
<path
d="M4 12L12 4M4 4L12 12"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
/**
* @description Icon/Outline/x
*/
export const XIcon = (props: Partial<IconProps>) => {
return <Icon component={X} {...props} />;
};
const Check = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
>
<path
d="M3.33337 8.66669L6.00004 11.3334L12.6667 4.66669"
stroke="#56C991"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
/**
* @description Icon/Outline/check
*/
export const CheckIcon = (props: Partial<IconProps>) => {
return <Icon component={Check} {...props} />;
};
const PencilAlt = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
>
<path
d="M7.33329 3.33334H3.99996C3.26358 3.33334 2.66663 3.93029 2.66663 4.66667V12C2.66663 12.7364 3.26358 13.3333 3.99996 13.3333H11.3333C12.0697 13.3333 12.6666 12.7364 12.6666 12V8.66667M11.7238 2.39052C12.2445 1.86983 13.0887 1.86983 13.6094 2.39052C14.1301 2.91122 14.1301 3.75544 13.6094 4.27614L7.88557 10H5.99996L5.99996 8.11438L11.7238 2.39052Z"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
/**
* @description Icon/Outline/pencil-alt
*/
export const PencilAltIcon = (props: Partial<IconProps>) => {
return <Icon component={PencilAlt} {...props} />;
};
const Refresh = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
>
<path
d="M2.66663 2.66669V6.00002H3.0543M13.292 7.33335C12.964 4.70248 10.7197 2.66669 7.99996 2.66669C5.76171 2.66669 3.84549 4.04547 3.0543 6.00002M3.0543 6.00002H5.99996M13.3333 13.3334V10H12.9456M12.9456 10C12.1544 11.9546 10.2382 13.3334 7.99996 13.3334C5.28021 13.3334 3.03595 11.2976 2.70789 8.66669M12.9456 10H9.99996"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
/**
* @description Icon/Outline/refresh
*/
export const RefreshIcon = (props: Partial<IconProps>) => {
return <Icon component={Refresh} {...props} />;
};
const Exit = () => (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9.33333 6.66668C9.10226 6.78107 8.87965 6.90988 8.66667 7.0519C7.05869 8.12418 6 9.95027 6 12.0227C6 15.3239 8.68629 18 12 18C15.3137 18 18 15.3239 18 12.0227C18 9.95027 16.9413 8.12418 15.3333 7.0519C15.1204 6.90988 14.8977 6.78107 14.6667 6.66668M12 5.33334V10.6667"
stroke="#F7544A"
strokeLinecap="round"
/>
</svg>
);
/**
* @description 退出
*/
export const ExitIcon = (props: Partial<IconProps>) => {
return <Icon component={Exit} {...props} />;
};
const PlusSm = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
>
<path
d="M8 4V8M8 8V12M8 8H12M8 8L4 8"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
/**
* @description Icon/Outline/plus-sm
*/
export const PlusSmIcon = (props: Partial<IconProps>) => {
return <Icon component={PlusSm} {...props} />;
};
const Trash = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
>
<path
d="M12.6666 4.66667L12.0884 12.7617C12.0386 13.4594 11.458 14 10.7585 14H5.24145C4.54193 14 3.96135 13.4594 3.91151 12.7617L3.33329 4.66667M6.66663 7.33333V11.3333M9.33329 7.33333V11.3333M9.99996 4.66667V2.66667C9.99996 2.29848 9.70148 2 9.33329 2H6.66663C6.29844 2 5.99996 2.29848 5.99996 2.66667V4.66667M2.66663 4.66667H13.3333"
stroke="#F7544A"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
/**
* @description Icon/Outline/trash
*/
export const TrashIcon = (props: Partial<IconProps>) => {
return <Icon component={Trash} {...props} />;
};
+73
View File
@@ -0,0 +1,73 @@
.Contro {
display: flex;
align-items: center;
height: 40px;
padding: 8px 16px;
font-size: 12px;
}
.Contro-error-bg {
background-color: rgba(244, 115, 107, .10);
}
.Contro-success-bg {
background-color: rgba(86, 201, 145, .10);
}
.Contro-cont-input {
height: 24px;
margin-right: 8px;
}
.Contro-lable {
color: #85899E;
}
.Contro-cont {
display: flex;
justify-content: space-between;
align-items: center;
flex: 1;
}
.Contro-cont-text-error {
color: #F6544A;
}
.Contro-cont-text-success {
color: #56C991;
}
.Contro-handle-icon {
display: flex;
align-items: center;
}
.Contro-handle-icon svg {
cursor: pointer;
}
.contro-handle-icon-check {
margin-left: 8px;
}
.icon-p {
display: inline-block;
padding: 4px;
}
.icon-p svg {
color: #85899E;
}
.Contro-cont span.anticon:hover {
background: #F0F1F3;
}
.grey-icon svg {
color: #85899E;
}
.icon-active:active svg {
color: #F28B44;
}
+191
View File
@@ -0,0 +1,191 @@
import React, {useEffect, useMemo, useRef, useState} from "react";
import {Divider, Tooltip, Input} from "antd";
import classNames from "classnames";
import {
CheckIcon,
ExitIcon,
PencilAltIcon,
RefreshIcon,
XIcon,
} from "@assets/icon/icon";
import {wsc} from "@network/chrome";
import "./Contro.css";
interface ControProps {
}
export const Contro: React.FC<ControProps> = () => {
const [isEdit, setIsEdit] = useState<boolean>(false);
const [connected, setConnected] = useState(false);
const [autoFindFailedReason, setAutoFindFailedReason] = useState<string>("");
const [enginePort, setEnginePort] = useState<string>("");
const [enginePortTemp, setEnginePortTemp] = useState<string>(enginePort);
const enginePortRef = useRef<string>(enginePort);
useEffect(() => {
enginePortRef.current = enginePort;
}, [enginePort]);
useEffect(() => {
const yakitConnectInfo = localStorage.getItem("yakit-connect");
if (!yakitConnectInfo) {
findPort(11212, 11222);
} else {
const {port, connected} = JSON.parse(yakitConnectInfo);
setConnected(connected);
setEnginePort(port + "");
setAutoFindFailedReason("");
}
wsc.onWSCMessage((message) => {
if (message.action === wsc.ActionType.STATUS) {
if (message.connected === false) {
handleConnectFail();
} else {
localStorage.setItem(
"yakit-connect",
JSON.stringify({connected: true, port: message.port})
);
setEnginePort(message.port + "");
setConnected(true);
setAutoFindFailedReason("");
}
}
});
}, []);
const findPort = (port: number, max: number) => {
const ws = new WebSocket(`ws://127.0.0.1:${port}`);
ws.onclose = (e: CloseEvent) => {
if (enginePortRef.current) {
handleConnectFail();
return;
}
if (e.reason !== `FoundYakitWebSocketController` && port + 1 <= max) {
setTimeout(() => findPort(port + 1, max), 200);
}
if (port + 1 > max) {
setConnected(false);
setEnginePort("");
setAutoFindFailedReason("Cannot found Yakit");
localStorage.setItem("yakit-connect", "");
}
};
ws.onopen = () => {
setConnected(true);
setEnginePort(port + "");
ws.close(1000, "FoundYakitWebSocketController");
connectPort(port);
};
};
const handleConnectFail = () => {
setConnected(false);
setAutoFindFailedReason("Yakit WebSocket Controller Connect Fail");
localStorage.setItem("yakit-connect", "");
};
const connectPort = (port: number) => {
setAutoFindFailedReason("");
wsc.connect(port);
};
const safeConnected = useMemo(() => {
return connected && enginePort;
}, [connected, enginePort]);
const failConnected = useMemo(() => {
return !connected && autoFindFailedReason;
}, [connected, autoFindFailedReason]);
return (
<>
{safeConnected || failConnected ? (
<div
className={classNames("Contro", {
["Contro-success-bg"]: safeConnected,
["Contro-error-bg"]: failConnected,
})}
>
<div className="Contro-lable">Yakit </div>
<div className="Contro-cont">
{isEdit ? (
<>
<Input
rootClassName="Contro-cont-input"
value={enginePortTemp}
placeholder="输入范围 11212 - 11222"
onChange={(e) => {
const value = e.target.value;
setEnginePortTemp(value);
}}
/>
<div className="Contro-handle-icon">
<XIcon
className="grey-icon icon-p icon-active"
onClick={() => {
setIsEdit(false);
}}
/>
<CheckIcon
className="contro-handle-icon-check icon-p"
onClick={() => {
if (enginePortTemp) {
setIsEdit(false);
setEnginePort(enginePortTemp);
connectPort(Number(enginePortTemp));
}
}}
/>
</div>
</>
) : (
<>
<div
className={classNames("Contro-cont-text", {
["Contro-cont-text-success"]: safeConnected,
["Contro-cont-text-error"]: failConnected,
})}
>
{safeConnected && "已连接(" + enginePort + ""}
{failConnected &&
(enginePort
? autoFindFailedReason + "" + enginePort + ""
: autoFindFailedReason)}
</div>
<div className="Contro-handle-icon">
<Tooltip title="修改监听端口">
<PencilAltIcon
className="grey-icon icon-p icon-active"
onClick={() => {
setIsEdit(true);
setEnginePortTemp(enginePort);
}}
/>
</Tooltip>
<Divider type="vertical" style={{height: 16}}/>
{safeConnected && (
<ExitIcon onClick={() => wsc.disconnect()}/>
)}
{failConnected && (
<RefreshIcon
className="grey-icon icon-active"
onClick={() => {
if (enginePort) {
connectPort(Number(enginePort));
} else {
findPort(11212, 11222);
}
}}
/>
)}
</div>
</>
)}
</div>
</div>
) : null}
</>
);
};
-95
View File
@@ -1,95 +0,0 @@
import React, {useEffect} from "react";
import {Alert, Button, Card, Form, InputNumber} from "antd";
import {wsc} from "../network/chrome";
import {useGetState} from "ahooks";
export interface ControllerProp {
}
export const Controller: React.FC<ControllerProp> = (props) => {
const [findingPort, setFindingPort] = React.useState<boolean>(false)
const [autoFindFailedReason, setAutoFindFailedReason] = React.useState<string>("")
const [port, setPort] = React.useState<number>()
const [connected, setConnected, getConnected] = useGetState(false);
const [init, setInit] = React.useState(false)
useEffect(() => {
// control status
const updateStatus = () => {
wsc.updateWSCStatus()
}
updateStatus()
const id = setInterval(updateStatus, 500)
wsc.onWSCMessage((req: { connected: boolean }) => {
if (req['connected'] === undefined) {
return
}
setConnected(req.connected)
setTimeout(() => {
setInit(true)
}, 500)
})
return () => {
clearInterval(id)
}
}, [])
useEffect(() => {
if (!init || connected) {
return
}
const findPort = (port: number, max: number) => {
const ws = new WebSocket(`ws://127.0.0.1:${port}`)
ws.onclose = (e: CloseEvent) => {
if (e.reason !== `FoundYakitWebSocketController` && (port + 1 <= max)) {
setTimeout(() => findPort(port + 1, max), 300)
}
if (port + 1 > max) {
setFindingPort(false)
setAutoFindFailedReason("Cannot found Yakit or Yakit WebSocket Controller Port is not right")
}
}
ws.onopen = () => {
setFindingPort(false)
setPort(port)
ws.close(1000, "FoundYakitWebSocketController")
}
}
findPort(11212, 11222)
}, [connected, init])
const freeze = findingPort || (!init);
const safeConnected = connected && init;
return <Card size={"small"} extra={safeConnected ? <Button size={"small"} danger={true} onClick={() => {
wsc.disconnect()
}}>
Disconnect
</Button> : undefined}>
{connected && init ?
<Alert type={"success"} message={"Yakit Connected"}/> :
<Form
onSubmitCapture={e => {
e.preventDefault()
wsc.connect(port)
}}
size={"small"}
>
{autoFindFailedReason !== '' ? <Alert type={"error"} message={autoFindFailedReason}/> : undefined}
<Form.Item label={"Controller Port"}>
<InputNumber disabled={freeze} value={port} onChange={e => setPort(e)}/>
</Form.Item>
<Form.Item>
<Button size={"small"} type={"primary"} loading={freeze} htmlType={"submit"}>Connect to
Yakit</Button>
</Form.Item>
</Form>}
</Card>
};
+77
View File
@@ -0,0 +1,77 @@
import React, {useEffect, useState} from "react";
import {Button, Input} from "antd";
import TextArea from "antd/lib/input/TextArea";
import {wsc} from "@network/chrome";
interface EvalInTabProps {
}
export const EvalInTab: React.FC<EvalInTabProps> = () => {
const [funcName, setFuncName] = useState("");
const [inputArgsData, setInputArgsData] = useState("");
const [code, setCode] = useState("");
useEffect(() => {
wsc.onWSCMessage((message) => {
if (message.action === wsc.ActionType.TO_EXTENSION_PAGE) {
console.log("res:", message.result);
alert("from content script: " + JSON.stringify(message.result));
}
});
}, []);
const handleClick = async () => {
try {
const [tab] = await wsc.getTab();
await chrome.runtime.sendMessage({
action: wsc.ActionType.INJECT_SCRIPT,
tabId: tab.id,
value: {mode: "CONTENT_CALL_FUNCTION", fn_name: funcName, args: inputArgsData},
});
} catch (error) {
}
};
const handleInputChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
setInputArgsData(event.target.value);
};
const handleEvalCodeClick = async () => {
try {
const [tab] = await wsc.getTab();
await chrome.runtime.sendMessage({
action: wsc.ActionType.INJECT_SCRIPT,
tabId: tab.id,
value: {
mode: "CONTENT_EVAL_CODE",
code: code,
},
});
} catch (error) {
}
}
return (
<div>
<Input
value={funcName}
onChange={(e) => setFuncName(e.target.value)}
placeholder="Enter function name"
></Input>
<TextArea
value={inputArgsData}
onChange={handleInputChange}
placeholder="Enter function args"
/>
<Button onClick={handleClick}>eval func in tab</Button>
<TextArea
value={code}
onChange={(e) => setCode(e.target.value)}
placeholder="Enter your expression (e.g., 22 * 33)"
/>
<Button onClick={handleEvalCodeClick}>eval code in tab</Button>
</div>
);
};
+113
View File
@@ -0,0 +1,113 @@
.Prox-title-wrap {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 24px 8px;
}
.Prox-title-wrap-left {
display: flex;
align-items: center;
}
.Prox-title-wrap-left .prox-title {
margin-right: 4px;
color: #31343F;
font-size: 14px;
font-weight: 600;
}
.Prox-title-wrap-left .prox-number {
display: inline-block;
width: 18px;
height: 16px;
text-align: center;
color: #85899E;
border-radius: 8px;
background: #F0F1F3;
font-size: 10px;
}
.Prox-title-wrap-right {
display: flex;
align-items: center;
cursor: pointer;
}
.Prox-title-wrap-right .Prox-add-text {
margin-right: 4px;
color: var(--yakit-primary);
font-size: 12px;
font-weight: 500;
}
.Prox-title-wrap-right .Prox-add-icon svg {
color: var(--yakit-primary);
}
.Prox-list-wrap {
overflow-y: auto;
max-height: 305px;
padding: 16px;
padding-top: 0;
}
.add-list {
display: flex;
justify-content: center;
align-items: center;
height: 28px;
cursor: pointer;
border: 1px solid #EAECF3;
border-radius: 4px;
background-color: #FFFFFF;
}
.add-list:hover {
border: 1px solid var(--yakit-primary);
}
.add-list .add-list-icon svg {
color: #85899E;
}
.Prox-list-wrap .add-list .add-list-text {
margin-left: 4px;
color: #31343F;
font-size: 14px;
font-weight: 600;
}
.Prox-list-wrap .Prox-list-item-wrap {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 8px;
border-radius: 8px;
}
.Prox-list-wrap .Prox-list-item-wrap:hover {
background-color: #F8F8F8;
}
.Prox-list-item-space .ant-space-item .ant-space-compact {
height: 28px;
}
.Prox-list-item-space .ant-space-item .ant-space-compact .ant-select-single {
height: 28px;
}
.Prox-list-wrap .Prox-list-item-wrap:hover .proxy-list-del-icon {
display: block;
}
.proxy-list-del-icon {
display: none;
cursor: pointer;
padding: 4px;
}
.proxy-list-del-icon:hover {
background: #F0F1F3;
}
+300 -83
View File
@@ -1,93 +1,310 @@
import React, {useEffect, useState} from "react";
import {Button, Card, Form, Input, InputNumber, Select, Space} from "antd";
import {wsc} from "../network/chrome";
export interface ProxifierProp {
}
const {Compact} = Space;
import React, { useEffect, useState } from "react";
import { Space, Select, Input, Switch } from "antd";
import { PlusSmIcon, TrashIcon } from "@assets/icon/icon";
import { wsc } from "@network/chrome";
import "./Proxifier.css";
type Scheme = "http" | "socks5";
interface ProxyConfig {
scheme: "http" | "socks5";
host: string;
port: number;
id: string;
scheme: Scheme;
host: string;
port: string;
hostStatus: "error" | "";
portStatus: "error" | "";
open: boolean;
proxy: string;
}
export const Proxifier: React.FC<ProxifierProp> = (props) => {
const [config, setConfig] = useState<ProxyConfig>({
scheme: "http",
host: "127.0.0.1",
port: 8083
});
const [init, setInit] = useState(false);
const [proxyEnable, setProxyEnable] = useState(false);
const [currentProxy, setCurrentProxy] = useState("");
export interface ProxifierProps {}
export const Proxifier: React.FC<ProxifierProps> = () => {
const [proxyList, setProxyList] = useState<ProxyConfig[]>(() => {
const storageProxyList = localStorage.getItem("yakit-proxy-list") || "[]";
return JSON.parse(storageProxyList);
});
useEffect(() => {
if (init) {
return
useEffect(() => {
localStorage.setItem("yakit-proxy-list", JSON.stringify(proxyList));
}, [proxyList]);
const addNewProxyListItem = (
scheme: Scheme,
host: string,
port: string,
open: boolean,
proxy: string
) => {
const proxyItem: ProxyConfig = {
id: Math.random() + "",
scheme: scheme as Scheme,
host: host,
port: port,
hostStatus: "",
portStatus: "",
open: open,
proxy: proxy,
};
return proxyItem;
};
const parseUrl = (url: string) => {
const regex = /^(.*?):\/\/(.*?):(\d+)/;
const match = url.match(regex);
if (match) {
const scheme = match[1];
const host = match[2];
const port = match[3];
return {
scheme,
host,
port,
};
} else {
return null; // 不匹配格式
}
};
useEffect(() => {
wsc.updateProxyStatus();
wsc.onProxyStatusMessage((msg) => {
if (msg.proxy === undefined || msg.enable === undefined) {
return;
}
if (msg.proxy === "" || msg.enable === false) {
if (proxyList.some((i) => i.open)) {
const copyProxyList = [...proxyList];
copyProxyList.forEach((i) => {
i.open = false;
});
setProxyList(copyProxyList);
}
return;
}
wsc.onProxyStatusMessage(msg => {
if (msg['proxy'] === undefined || msg['enable'] === undefined) {
return
if (msg.proxy && msg.enable) {
const copyProxyList = [...proxyList];
let newProxyItem: ProxyConfig = undefined;
if (!copyProxyList.length) {
const urlObj = parseUrl(msg.proxy);
if (urlObj) {
newProxyItem = addNewProxyListItem(
urlObj.scheme as Scheme,
urlObj.host,
urlObj.port,
true,
msg.proxy
);
}
} else {
const proxyExist = copyProxyList.some((i) => i.proxy === msg.proxy);
if (proxyExist) {
const proxyOpen = copyProxyList.some(
(i) => i.open && i.proxy === msg.proxy
);
if (!proxyOpen) {
copyProxyList.forEach((i) => {
i.open = false;
});
for (let i = 0; i < copyProxyList.length; i++) {
if (copyProxyList[i].proxy === msg.proxy) {
copyProxyList[i].open = true;
break;
}
}
}
setInit(true)
setProxyEnable(msg.enable)
setCurrentProxy(msg.proxy)
})
}, [init])
// proxy
useEffect(() => {
const update = () => {
wsc.updateProxyStatus()
} else {
copyProxyList.forEach((i) => {
i.open = false;
});
const urlObj = parseUrl(msg.proxy);
if (urlObj) {
newProxyItem = addNewProxyListItem(
urlObj.scheme as Scheme,
urlObj.host,
urlObj.port,
true,
msg.proxy
);
}
}
}
update()
const id = setInterval(update, 500)
return () => {
clearInterval(id)
}
}, [])
return <Card title={`Proxy Set: ${proxyEnable ? currentProxy : "Non-Config"}`} size={"small"} extra={<>{
proxyEnable ? <Button size={"small"} onClick={() => {
wsc.clearproxy()
}}></Button> : undefined
}</>}>
<Form size={"small"} onSubmitCapture={e => {
e.preventDefault()
setInit(false)
wsc.setproxy(config.scheme, config.host, config.port)
}} disabled={!init || proxyEnable}>
<Form.Item label={"Proxy Setting"}>
<Compact>
<Select
style={{width: 86}}
value={config.scheme}
onChange={e => (setConfig({...config, scheme: e}))}
>
<Select.Option value={"http"}>HTTP</Select.Option>
<Select.Option value={"socks5"}>Socks5</Select.Option>
</Select>
<Input
style={{width: 100}}
placeholder={"ProxyHost"}
value={config.host}
onChange={e => (setConfig({...config, host: e.target.value}))}/>
<InputNumber
style={{width: 65}}
placeholder={"Port"}
value={config.port}
onChange={e => (setConfig({...config, port: e}))}
/>
</Compact>
</Form.Item>
<Form.Item>
<Button type={"primary"} htmlType={"submit"} loading={!init}>Enable Proxy</Button>
</Form.Item>
</Form>
</Card>
};
if (newProxyItem) {
copyProxyList.unshift(newProxyItem);
}
setProxyList(copyProxyList);
}
});
}, []);
const hostOnchange = (value: string, id: string) => {
const ipPattern = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
const domainPattern = /^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
const copyProxyList = structuredClone(proxyList);
if (value === "" || ipPattern.test(value) || domainPattern.test(value)) {
copyProxyList.forEach((i) => {
if (i.id === id) {
i.hostStatus = "";
i.host = value;
i.proxy = i.scheme + "://" + value + ":" + i.port;
}
});
} else {
copyProxyList.forEach((i) => {
if (i.id === id) {
i.hostStatus = "error";
i.host = value;
i.proxy = i.scheme + "://" + value + ":" + i.port;
}
});
}
setProxyList(copyProxyList);
};
const portOnchange = (value: string, id: string) => {
const portNumber = parseInt(value, 10);
const copyProxyList = structuredClone(proxyList);
if (
value === "" ||
(/^\d+$/.test(value) && portNumber >= 0 && portNumber <= 65535)
) {
copyProxyList.forEach((i) => {
if (i.id === id) {
i.portStatus = "";
const port = value === "" ? "" : portNumber + "";
i.port = port;
i.proxy = i.scheme + "://" + i.host + ":" + port;
}
});
} else {
copyProxyList.forEach((i) => {
if (i.id === id) {
i.portStatus = "error";
i.port = value;
i.proxy = i.scheme + "://" + i.host + ":" + value;
}
});
}
setProxyList(copyProxyList);
};
return (
<div className="Prox">
<div className="Prox-title-wrap">
<div className="Prox-title-wrap-left">
<span className="prox-title"></span>
<span className="prox-number">{proxyList.length}</span>
</div>
<div
className="Prox-title-wrap-right"
onClick={() => {
setProxyList([
...proxyList,
addNewProxyListItem("http", "", "", false, "http://"),
]);
}}
>
<span className="Prox-add-text"></span>
<PlusSmIcon className="Prox-add-icon" />
</div>
</div>
<div className="Prox-list-wrap">
{proxyList.length ? (
proxyList.map((item) => (
<div className="Prox-list-item-wrap" key={item.id}>
<Space className="Prox-list-item-space">
<Space.Compact>
<Select
value={item.scheme}
style={{ width: 88 }}
disabled={item.open}
onChange={(value, option) => {
const copyProxyList = structuredClone(proxyList);
copyProxyList.forEach((i) => {
if (i.id === item.id) {
i.scheme = value;
i.proxy = value + "://" + i.host + ":" + i.port;
}
});
setProxyList(copyProxyList);
}}
>
<Select.Option value="http">HTTP</Select.Option>
<Select.Option value="socks5">Socks5</Select.Option>
</Select>
<Input
value={item.host}
style={{ width: 136 }}
disabled={item.open}
status={item.hostStatus}
onChange={(e) => hostOnchange(e.target.value, item.id)}
/>
<Input
value={item.port}
style={{ width: 64 }}
disabled={item.open}
status={item.portStatus}
onChange={(e) => portOnchange(e.target.value, item.id)}
/>
</Space.Compact>
</Space>
{!item.open && (
<TrashIcon
className="proxy-list-del-icon"
onClick={() => {
setProxyList(proxyList.filter((i) => i.id !== item.id));
}}
/>
)}
<Switch
checkedChildren="启"
unCheckedChildren="停"
value={item.open}
disabled={
item.hostStatus === "error" ||
item.portStatus === "error" ||
item.host === "" ||
item.port === ""
}
onChange={(checked: boolean) => {
wsc.clearProxy();
const copyProxyList = structuredClone(proxyList);
copyProxyList.forEach((i) => {
if (i.id === item.id) {
i.open = checked;
if (checked) {
wsc.setProxy(item.scheme, item.host, Number(item.port));
}
} else {
i.open = false;
}
});
setProxyList(copyProxyList);
}}
/>
</div>
))
) : (
<div
className="add-list"
onClick={() => {
setProxyList([
addNewProxyListItem(
"http",
"127.0.0.1",
"8083",
false,
"http://127.0.0.1:8083"
),
]);
}}
>
<PlusSmIcon className="add-list-icon" />
<span className="add-list-text"></span>
</div>
)}
</div>
</div>
);
};
+29 -3
View File
@@ -1,3 +1,7 @@
html:root {
--yakit-primary: #F28B44
}
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
@@ -7,7 +11,29 @@ body {
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
*,
*::before,
*::after {
box-sizing: border-box;
}
::-webkit-scrollbar {
/*滚动条整体样式*/
width: 8px;
/*高宽分别对应横竖滚动条的尺寸*/
height: 8px;
}
::-webkit-scrollbar-thumb {
/*滚动条里面小方块*/
border-radius: 10px;
box-shadow: inset 0 0 5px rgba(193, 193, 193);
background: #c1c1c1;
}
::-webkit-scrollbar-track {
/*滚动条里面轨道*/
box-shadow: inset 0 0 5px rgba(193, 193, 193);
border-radius: 10px;
background: #ededed;
}
+24 -11
View File
@@ -1,9 +1,19 @@
import {chrome} from "./chromeapi";
export namespace wsc {
export enum ActionType {
CONNECT = 'connect',
DISCONNECT = 'disconnect',
STATUS = 'status',
PROXY_STATUS = 'proxy_status',
SET_PROXY = 'set_proxy',
CLEAR_PROXY = 'clear_proxy',
INJECT_SCRIPT = 'yakit_inject_script',
// 用于接收来自content script的消息
TO_EXTENSION_PAGE = "yakit_to_extension_page",
}
export function connect(port: number, host?: string) {
chrome.runtime.sendMessage({
action: 'connect',
action: ActionType.CONNECT,
host: host || '127.0.0.1',
port: port,
});
@@ -11,19 +21,19 @@ export namespace wsc {
export function disconnect() {
chrome.runtime.sendMessage({
action: 'disconnect',
action: ActionType.DISCONNECT,
});
}
export function updateWSCStatus() {
chrome.runtime.sendMessage({
action: 'status',
action: ActionType.STATUS,
});
}
export function updateProxyStatus() {
chrome.runtime.sendMessage({
action: 'proxystatus',
action: ActionType.PROXY_STATUS,
});
}
@@ -35,13 +45,16 @@ export namespace wsc {
chrome.runtime.onMessage.addListener(onMessage)
}
export function setproxy(scheme: string, host: string, port: number) {
chrome.runtime.sendMessage({action: "setproxy", scheme, host, port})
export function setProxy(scheme: string, host: string, port: number) {
chrome.runtime.sendMessage({action: ActionType.SET_PROXY, scheme, host, port})
}
export function clearproxy() {
chrome.runtime.sendMessage({action: 'clearproxy'})
export function clearProxy() {
chrome.runtime.sendMessage({action: ActionType.CLEAR_PROXY})
}
// 获取当前tab
export async function getTab() {
return chrome.tabs.query({active: true, currentWindow: true})
}
}
-74
View File
@@ -1,74 +0,0 @@
export interface MockMessageSender {
id?: string;
}
export interface MockChromeRuntime {
sendMessage: (message: any, responseCallback?: (response: any) => void) => void;
onMessage: {
addListener: (callback: (message: any, sender: MockMessageSender, sendResponse: (response?: any) => void) => void) => void;
};
}
export interface MockChromeProxy {
settings: {
set: (value: { value: any, scope: string }) => void;
get: (details: { incognito?: boolean }, callback: (config: any) => void) => void;
};
}
export interface MockChromeTabs {
query: (queryInfo: any, callback: (result: any) => void) => void;
create: (createProperties: any, callback?: (tab: any) => void) => void;
}
export interface MockChrome {
runtime: MockChromeRuntime;
proxy: MockChromeProxy;
tabs: MockChromeTabs;
}
declare global {
interface Window {
chrome?: any;
}
}
export const chrome: MockChrome = typeof window.chrome !== 'undefined' && window.chrome.runtime ? window.chrome : {
runtime: {
sendMessage: (message, responseCallback) => {
console.log('Mock sendMessage called with:', message);
if (responseCallback) responseCallback('response from mock');
},
onMessage: {
addListener: (callback) => {
console.log('Mock onMessage.addListener called');
callback({connected: true}, undefined, undefined)
// You can simulate incoming messages here if needed
}
}
},
proxy: {
settings: {
set: (value) => {
console.log('Mock proxy settings set with:', value);
},
get: (details, callback) => {
console.log('Mock proxy settings get called');
// Simulate proxy settings response
callback({mode: 'direct'});
}
}
},
tabs: {
query: (queryInfo, callback) => {
console.log('Mock tabs.query called with:', queryInfo);
// Simulate a tab query response
callback([{id: 1, url: 'http://example.com', title: 'Example'}]);
},
create: (createProperties, callback) => {
console.log('Mock tabs.create called with:', createProperties);
// Simulate creating a tab
if (callback) callback({id: 2, url: createProperties.url});
}
}
};
+10 -4
View File
@@ -1,6 +1,5 @@
{
"compilerOptions": {
"outDir": "./dist/",
"noImplicitAny": true,
"module": "es6",
"target": "es5",
@@ -8,9 +7,16 @@
"allowJs": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"moduleResolution": "node"
"moduleResolution": "node",
"baseUrl": "./",
"paths": {
"@*": ["src/*"]
}
},
"include": [
"./src/**/*"
"./src/**/*",
],
"exclude": [
"node_modules"
]
}
}
+62 -57
View File
@@ -5,60 +5,65 @@ const CopyWebpackPlugin = require('copy-webpack-plugin');
const webpack = require('webpack');
module.exports = {
mode: 'development', // 设置模式为开发模式
entry: './src/index.jsx', // 指定入口文件
output: {
path: path.resolve(__dirname, 'build'), // 输出目录
filename: 'bundle.js', // 输出文件名
},
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
'process.env.BABEL_ENV': JSON.stringify(process.env.BABEL_ENV || 'development'),
}),
new HtmlWebpackPlugin({
template: './public/index.html',
filename: 'index.html'
}),
new CopyWebpackPlugin({
patterns: [
// copy public assets exclude index.html
{
from: path.resolve(__dirname, 'public'),
to: path.resolve(__dirname, 'build'),
globOptions: {
ignore: ['**/index.html']
}
}
]
})
],
watch: true, // 开启实时监控
module: {
rules: [
{
test: /\.css$/, // 匹配所有的 css 文件
use: ['style-loader', 'css-loader'] // 对匹配到的文件使用这两个 loader
},
{
test: /\.tsx?$/, // 匹配TS和TSX文件
use: 'ts-loader',
exclude: /node_modules/,
},
{
test: /\.(js|jsx)$/, // 匹配JS和JSX文件
exclude: /node_modules/, // 排除node_modules目录
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-react'] // 使用的babel预设
}
}
}
]
},
resolve: {
extensions: ['.tsx', '.ts', '.js', '.jsx'] // 解析扩展(确保能够解析JS和JSX文件)
},
devtool: 'inline-source-map', // 生成内联源映射,便于调试
};
mode: 'development', // 设置模式为开发模式
entry: './src/index.jsx', // 指定入口文件
output: {
path: path.resolve(__dirname, 'build'), // 输出目录
filename: 'bundle.js', // 输出文件名
},
plugins: [
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
'process.env.BABEL_ENV': JSON.stringify(process.env.BABEL_ENV || 'development'),
}),
new HtmlWebpackPlugin({
template: './public/index.html',
filename: 'index.html'
}),
new CopyWebpackPlugin({
patterns: [
// copy public assets exclude index.html
{
from: path.resolve(__dirname, 'public'),
to: path.resolve(__dirname, 'build'),
globOptions: {
ignore: ['**/index.html']
}
}
]
})
],
watch: true, // 开启实时监控
module: {
rules: [
{
test: /\.css$/, // 匹配所有的 css 文件
use: ['style-loader', 'css-loader'] // 对匹配到的文件使用这两个 loader
},
{
test: /\.tsx?$/, // 匹配TS和TSX文件
use: 'ts-loader',
exclude: /node_modules/,
},
{
test: /\.(js|jsx)$/, // 匹配JS和JSX文件
exclude: /node_modules/, // 排除node_modules目录
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-react'] // 使用的babel预设
}
}
}
]
},
resolve: {
extensions: ['.tsx', '.ts', '.js', '.jsx'], // 解析扩展(确保能够解析JS和JSX文件)
alias: {
'@assets': path.resolve(__dirname, './src/assets'),
'@components': path.resolve(__dirname, './src/components'),
'@network': path.resolve(__dirname, './src/network'),
}
},
devtool: 'inline-source-map', // 生成内联源映射,便于调试
};