Compare commits

...
Author SHA1 Message Date
go0p bbcbdd1d81 add build action 2025-03-02 14:45:10 +08:00
go0p cb05ab70e3 format 2024-05-15 14:43:04 +08:00
go0p 207e8172f1 eval done && fix findDOMNode warning 2024-05-15 14:43:04 +08:00
go0p 6358fc720d 完善一下 2024-05-15 14:43:04 +08:00
song_xiao_lin 26c3bbc570 del: 删除无用代码 2024-05-15 14:43:04 +08:00
song_xiao_lin 5fbd51a073 del: 删除无用代码 2024-05-15 14:43:04 +08:00
song_xiao_lin 7c1cdd562c fix: 注入js 2024-05-15 14:43:04 +08:00
song_xiao_lin a4840c9c5e fix: 重复监听 2024-05-15 14:43:04 +08:00
go0p e150379c7f 调用active tab js func 2024-05-15 14:43:04 +08:00
go0p 0c8f82b441 fix 2024-05-15 14:43:04 +08:00
go0p fb0a20b065 eval in page context 2024-05-15 14:43:04 +08:00
song_xiao_lin c950ede429 pref 2024-05-15 14:43:04 +08:00
song_xiao_lin 50704b68ac feat: 删掉提示 2024-05-15 14:43:04 +08:00
song_xiao_lin 52a8d315d1 fix: 状态问题 2024-05-15 14:43:04 +08:00
song_xiao_lin ddf036da66 fix: 失败重连问题 2024-05-15 14:43:04 +08:00
song_xiao_lin ea338fe547 fix: 断开连接 2024-05-15 14:43:04 +08:00
song_xiao_lin ab4f587eda fix: 部分问题 2024-05-15 14:43:04 +08:00
song_xiao_lin 289dcdf030 fix: 修复重连问题 2024-05-15 14:43:04 +08:00
go0p ac36e0bfc7 inject script 2024-05-15 14:43:04 +08:00
go0p 8cdd5605bc inject script 2024-05-15 14:43:04 +08:00
go0p f6cf369b3a add inject code 2024-05-15 14:43:04 +08:00
go0p 8e154aae29 feat(wsc): fix 2024-05-15 14:43:04 +08:00
song_xiao_lin 523889014f zancun 2024-05-15 14:43:04 +08:00
song_xiao_lin 42b0e958ba feat: Contro逻辑调整 2024-05-15 14:43:04 +08:00
song_xiao_lin 8d61a5f5e8 style: icon点击样式给主题色 2024-05-15 14:43:04 +08:00
song_xiao_lin 0fddcc959c feat: Contro 2024-05-15 14:43:04 +08:00
song_xiao_lin 7af4124da8 feat: 代理功能 2024-05-15 14:43:04 +08:00
song_xiao_lin 06fff8f92e style: 样式调整 2024-05-15 14:43:04 +08:00
24 changed files with 1549 additions and 14566 deletions
+69
View File
@@ -0,0 +1,69 @@
name: Build and Release
on:
# 只保留手动触发工作流
workflow_dispatch:
inputs:
branch:
description: '选择要构建的分支'
required: true
default: 'main'
type: string
jobs:
build-and-release:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.branch }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'yarn' # 改为 yarn 缓存
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Build project
run: yarn build
- name: Zip build artifacts
run: |
cd dist
zip -r ../extension.zip .
- name: Generate version number
id: version
run: |
echo "version=1.0.${{ github.run_number }}" >> $GITHUB_OUTPUT
echo "build_time=$(date +'%Y-%m-%d %H:%M:%S')" >> $GITHUB_OUTPUT
- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: v${{ steps.version.outputs.version }}
release_name: Release v${{ steps.version.outputs.version }}
body: |
Branch: ${{ github.event.inputs.branch }}
Commit: ${{ github.sha }}
Build Time: ${{ steps.version.outputs.build_time }}
draft: false
prerelease: false
- name: Upload Release Asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./extension.zip
asset_name: yakit-chrome-extension-v${{ steps.version.outputs.version }}.zip
asset_content_type: application/zip
+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', // 生成内联源映射,便于调试
};