权限确认
This commit is contained in:
parent
2a0286e47d
commit
44b6f11b19
@ -35,6 +35,7 @@ export default defineConfig({
|
||||
inject: `${src}/inject.ts`,
|
||||
popup: `${src}/pages/popup/main.tsx`,
|
||||
install: `${src}/pages/install/main.tsx`,
|
||||
confirm: `${src}/pages/confirm/main.tsx`,
|
||||
options: `${src}/pages/options/main.tsx`,
|
||||
"editor.worker": "monaco-editor/esm/vs/editor/editor.worker.js",
|
||||
"ts.worker": "monaco-editor/esm/vs/language/typescript/ts.worker.js",
|
||||
@ -151,6 +152,15 @@ export default defineConfig({
|
||||
minify: true,
|
||||
chunks: ["install"],
|
||||
}),
|
||||
,
|
||||
new rspack.HtmlRspackPlugin({
|
||||
filename: `${dist}/ext/src/confirm.html`,
|
||||
template: `${src}/pages/template.html`,
|
||||
inject: "head",
|
||||
title: "Confirm - ScriptCat",
|
||||
minify: true,
|
||||
chunks: ["confirm"],
|
||||
}),
|
||||
new rspack.HtmlRspackPlugin({
|
||||
filename: `${dist}/ext/src/options.html`,
|
||||
template: `${src}/pages/options.html`,
|
||||
|
@ -3,7 +3,7 @@ import Logger from "@App/app/logger/logger";
|
||||
import { Script, ScriptDAO } from "@App/app/repo/scripts";
|
||||
import { ExtMessageSender, GetSender, Group, MessageSend } from "@Packages/message/server";
|
||||
import { ValueService } from "@App/app/service/service_worker/value";
|
||||
import PermissionVerify from "./permission_verify";
|
||||
import PermissionVerify, { ConfirmParam } from "./permission_verify";
|
||||
import { connect, sendMessage } from "@Packages/message/client";
|
||||
import Cache, { incr } from "@App/app/cache";
|
||||
import EventEmitter from "eventemitter3";
|
||||
@ -13,6 +13,7 @@ import { getIcon, isFirefox } from "@App/pkg/utils/utils";
|
||||
import { PopupService } from "./popup";
|
||||
import { act } from "react";
|
||||
import { MockMessageConnect } from "@Packages/message/mock_message";
|
||||
import i18next, { i18nName } from "@App/locales/locales";
|
||||
|
||||
// GMApi,处理脚本的GM API调用请求
|
||||
|
||||
@ -357,8 +358,35 @@ export default class GMApi {
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: maxRedirects实现
|
||||
@PermissionVerify.API()
|
||||
@PermissionVerify.API({
|
||||
confirm: async (request: Request) => {
|
||||
console.log("confirm", request);
|
||||
const config = <GMSend.XHRDetails>request.params[0];
|
||||
const url = new URL(config.url);
|
||||
if (request.script.metadata.connect) {
|
||||
const { connect } = request.script.metadata;
|
||||
for (let i = 0; i < connect.length; i += 1) {
|
||||
if (url.hostname.endsWith(connect[i])) {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
const metadata: { [key: string]: string } = {};
|
||||
metadata[i18next.t("script_name")] = i18nName(request.script);
|
||||
metadata[i18next.t("request_domain")] = url.hostname;
|
||||
metadata[i18next.t("request_url")] = config.url;
|
||||
|
||||
return Promise.resolve({
|
||||
permission: "cors",
|
||||
permissionValue: url.hostname,
|
||||
title: i18next.t("script_accessing_cross_origin_resource"),
|
||||
metadata,
|
||||
describe: i18next.t("confirm_operation_description"),
|
||||
wildcard: true,
|
||||
permissionContent: i18next.t("domain"),
|
||||
} as ConfirmParam);
|
||||
},
|
||||
})
|
||||
async GM_xmlhttpRequest(request: Request, sender: GetSender) {
|
||||
if (request.params.length === 0) {
|
||||
throw new Error("param is failed");
|
||||
|
@ -183,6 +183,7 @@ export default class PermissionVerify {
|
||||
}
|
||||
return Promise.resolve(model);
|
||||
});
|
||||
console.log("confirm", request, confirm);
|
||||
// 有查询到结果,进入判断,不再需要用户确认
|
||||
if (ret) {
|
||||
if (ret.allow) {
|
||||
@ -249,9 +250,27 @@ export default class PermissionVerify {
|
||||
|
||||
// 弹出窗口让用户进行确认
|
||||
async confirmWindow(script: Script, confirm: ConfirmParam): Promise<UserConfirm> {
|
||||
return Promise.resolve({
|
||||
allow: true,
|
||||
type: 1,
|
||||
return new Promise((resolve, reject) => {
|
||||
const uuid = uuidv4();
|
||||
// 超时处理
|
||||
const timeout = setTimeout(() => {
|
||||
this.confirmMap.delete(uuid);
|
||||
reject(new Error("permission confirm timeout"));
|
||||
}, 40 * 1000);
|
||||
// 保存到map中
|
||||
this.confirmMap.set(uuid, {
|
||||
confirm,
|
||||
script,
|
||||
resolve: (value: UserConfirm) => {
|
||||
clearTimeout(timeout);
|
||||
resolve(value);
|
||||
},
|
||||
reject,
|
||||
});
|
||||
// 打开窗口
|
||||
chrome.tabs.create({
|
||||
url: chrome.runtime.getURL(`src/confirm.html?uuid=${uuid}`),
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -10,10 +10,11 @@ import zhTW from "./zh-TW/translation.json";
|
||||
import achUG from "./ach-UG/translation.json";
|
||||
import "dayjs/locale/zh-cn";
|
||||
import "dayjs/locale/zh-tw";
|
||||
import { systemConfig } from "@App/pages/store/global";
|
||||
|
||||
i18n.use(initReactI18next).init({
|
||||
fallbackLng: "zh-CN",
|
||||
lng: localStorage.language || chrome.i18n.getUILanguage(),
|
||||
lng: chrome.i18n.getUILanguage(),
|
||||
interpolation: {
|
||||
escapeValue: false, // react already safes from xss => https://www.i18next.com/translation-function/interpolation#unescape
|
||||
},
|
||||
@ -26,22 +27,13 @@ i18n.use(initReactI18next).init({
|
||||
},
|
||||
});
|
||||
|
||||
if (!localStorage.language) {
|
||||
chrome.i18n.getAcceptLanguages((lngs) => {
|
||||
// 遍历数组寻找匹配语言
|
||||
for (let i = 0; i < lngs.length; i += 1) {
|
||||
const lng = lngs[i];
|
||||
if (i18n.hasResourceBundle(lng, "translation")) {
|
||||
localStorage.language = lng;
|
||||
chrome.i18n.getAcceptLanguages((lngs) => {
|
||||
systemConfig.getLanguage().then((lng) => {
|
||||
i18n.changeLanguage(lng);
|
||||
dayjs.locale(lng.toLocaleLowerCase());
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
dayjs.locale((localStorage.language as string).toLocaleLowerCase());
|
||||
}
|
||||
});
|
||||
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
export function i18nName(script: { name: string; metadata: Metadata }) {
|
||||
|
@ -3,7 +3,8 @@ import { Button, Card, Collapse, Link, Message, Space, Typography } from "@arco-
|
||||
import { useTranslation } from "react-i18next";
|
||||
import FileSystemParams from "../FileSystemParams";
|
||||
import { systemConfig } from "@App/pages/store/global";
|
||||
import { FileSystemType } from "@Packages/filesystem/factory";
|
||||
import FileSystemFactory, { FileSystemType } from "@Packages/filesystem/factory";
|
||||
import { set } from "node_modules/yaml/dist/schema/yaml-1.1/set";
|
||||
|
||||
const CollapseItem = Collapse.Item;
|
||||
|
||||
@ -52,13 +53,13 @@ const GMApiSetting: React.FC = () => {
|
||||
Message.error(`${t("account_validation_failed")}: ${e}`);
|
||||
return;
|
||||
}
|
||||
const params = { ...systemConfig.catFileStorage.params };
|
||||
const params = { ...fileSystemParams };
|
||||
params[fileSystemType] = fileSystemParams;
|
||||
systemConfig.catFileStorage = {
|
||||
systemConfig.setCatFileStorage({
|
||||
status: "success",
|
||||
filesystem: fileSystemType,
|
||||
params,
|
||||
};
|
||||
});
|
||||
setStatus("success");
|
||||
Message.success(t("save_success")!);
|
||||
}}
|
||||
@ -68,10 +69,14 @@ const GMApiSetting: React.FC = () => {
|
||||
<Button
|
||||
key="reset"
|
||||
onClick={() => {
|
||||
const config = systemConfig.catFileStorage;
|
||||
config.status = "unset";
|
||||
systemConfig.catFileStorage = config;
|
||||
systemConfig.setCatFileStorage({
|
||||
status: "unset",
|
||||
filesystem: "webdav",
|
||||
params: {},
|
||||
});
|
||||
setStatus("unset");
|
||||
setFilesystemParam({});
|
||||
setFilesystemType("webdav");
|
||||
}}
|
||||
type="primary"
|
||||
status="danger"
|
||||
|
142
src/pages/confirm/App.tsx
Normal file
142
src/pages/confirm/App.tsx
Normal file
@ -0,0 +1,142 @@
|
||||
import { ConfirmParam } from "@App/app/service/service_worker/permission_verify";
|
||||
import { Button, Message, Space } from "@arco-design/web-react";
|
||||
import React, { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
function App() {
|
||||
const uuid = window.location.search.split("=")[1];
|
||||
const [confirm, setConfirm] = React.useState<ConfirmParam>();
|
||||
const [likeNum, setLikeNum] = React.useState(0);
|
||||
const [second, setSecond] = React.useState(30);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (second === 0) {
|
||||
window.close();
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
setSecond(second - 1);
|
||||
}, 1000);
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener("beforeunload", () => {
|
||||
permissionCtrl.sendConfirm(uuid, {
|
||||
allow: false,
|
||||
type: 0,
|
||||
});
|
||||
});
|
||||
|
||||
permissionCtrl
|
||||
.getConfirm(uuid)
|
||||
.then((data) => {
|
||||
setConfirm(data.confirm);
|
||||
setLikeNum(data.likeNum);
|
||||
})
|
||||
.catch((e: any) => {
|
||||
Message.error(e.message || t("get_confirm_error"));
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleConfirm = (allow: boolean, type: number) => {
|
||||
return async () => {
|
||||
try {
|
||||
await permissionCtrl.sendConfirm(uuid, {
|
||||
allow,
|
||||
type,
|
||||
});
|
||||
window.close();
|
||||
} catch (e: any) {
|
||||
Message.error(e.message || t("confirm_error"));
|
||||
setTimeout(() => {
|
||||
window.close();
|
||||
}, 3000);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="h-full">
|
||||
<Space direction="vertical">
|
||||
<span className="text-2xl font-500">{confirm?.title}</span>
|
||||
{confirm &&
|
||||
confirm.metadata &&
|
||||
Object.keys(confirm.metadata).map((key) => (
|
||||
<span className="text-base" key={key}>
|
||||
{key}: {confirm!.metadata![key]}
|
||||
</span>
|
||||
))}
|
||||
<span className="text-xl font-500">{confirm?.describe}</span>
|
||||
<div>
|
||||
<Button type="primary" onClick={handleConfirm(false, 1)}>
|
||||
{t("ignore")} ({second})
|
||||
</Button>
|
||||
</div>
|
||||
<div>
|
||||
<Space>
|
||||
<Button onClick={handleConfirm(true, 1)} status="success">
|
||||
{t("allow_once")}
|
||||
</Button>
|
||||
<Button onClick={handleConfirm(true, 3)} status="success">
|
||||
{t("temporary_allow", {
|
||||
permissionContent: confirm?.permissionContent,
|
||||
})}
|
||||
</Button>
|
||||
{likeNum > 2 && (
|
||||
<Button onClick={handleConfirm(true, 2)} status="success">
|
||||
{t("temporary_allow_all", {
|
||||
permissionContent: confirm?.permissionContent,
|
||||
})}
|
||||
</Button>
|
||||
)}
|
||||
<Button onClick={handleConfirm(true, 5)} status="success">
|
||||
{t("permanent_allow", {
|
||||
permissionContent: confirm?.permissionContent,
|
||||
})}
|
||||
</Button>
|
||||
{likeNum > 2 && (
|
||||
<Button onClick={handleConfirm(true, 4)} status="success">
|
||||
{t("permanent_allow_all", {
|
||||
permissionContent: confirm?.permissionContent,
|
||||
})}
|
||||
</Button>
|
||||
)}
|
||||
</Space>
|
||||
</div>
|
||||
<div>
|
||||
<Space>
|
||||
<Button onClick={handleConfirm(false, 1)} status="danger">
|
||||
{t("deny_once")}
|
||||
</Button>
|
||||
<Button onClick={handleConfirm(false, 3)} status="danger">
|
||||
{t("temporary_deny", {
|
||||
permissionContent: confirm?.permissionContent,
|
||||
})}
|
||||
</Button>
|
||||
{likeNum > 2 && (
|
||||
<Button onClick={handleConfirm(false, 2)} status="danger">
|
||||
{t("temporary_deny_all", {
|
||||
permissionContent: confirm?.permissionContent,
|
||||
})}
|
||||
</Button>
|
||||
)}
|
||||
<Button onClick={handleConfirm(false, 5)} status="danger">
|
||||
{t("permanent_deny", {
|
||||
permissionContent: confirm?.permissionContent,
|
||||
})}
|
||||
</Button>
|
||||
{likeNum > 2 && (
|
||||
<Button onClick={handleConfirm(false, 4)} status="danger">
|
||||
{t("permanent_deny_all", {
|
||||
permissionContent: confirm?.permissionContent,
|
||||
})}
|
||||
</Button>
|
||||
)}
|
||||
</Space>
|
||||
</div>
|
||||
</Space>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
33
src/pages/confirm/main.tsx
Normal file
33
src/pages/confirm/main.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import App from "./App.tsx";
|
||||
import MainLayout from "../components/layout/MainLayout.tsx";
|
||||
import "@arco-design/web-react/dist/css/arco.css";
|
||||
import "@App/locales/locales";
|
||||
import "@App/index.css";
|
||||
import { Provider } from "react-redux";
|
||||
import { store } from "@App/pages/store/store.ts";
|
||||
import LoggerCore from "@App/app/logger/core.ts";
|
||||
import migrate from "@App/app/migrate.ts";
|
||||
import { LoggerDAO } from "@App/app/repo/logger.ts";
|
||||
import DBWriter from "@App/app/logger/db_writer.ts";
|
||||
|
||||
// 初始化数据库
|
||||
migrate();
|
||||
// 初始化日志组件
|
||||
const loggerCore = new LoggerCore({
|
||||
writer: new DBWriter(new LoggerDAO()),
|
||||
labels: { env: "install" },
|
||||
});
|
||||
|
||||
loggerCore.logger().debug("page start");
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
||||
<React.StrictMode>
|
||||
<Provider store={store}>
|
||||
<MainLayout className="!flex-col !px-4 box-border">
|
||||
<App />
|
||||
</MainLayout>
|
||||
</Provider>
|
||||
</React.StrictMode>
|
||||
);
|
@ -10,9 +10,9 @@ import i18n from "@App/locales/locales";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import dayjs from "dayjs";
|
||||
import Logger from "@App/app/logger/logger";
|
||||
import { systemConfig } from "@App/pages/store/global";
|
||||
import { FileSystemType } from "@Packages/filesystem/factory";
|
||||
import FileSystemFactory, { FileSystemType } from "@Packages/filesystem/factory";
|
||||
import FileSystemParams from "@App/pages/components/FileSystemParams";
|
||||
import { systemConfig } from "@App/pages/store/global";
|
||||
|
||||
function Setting() {
|
||||
const [syncDelete, setSyncDelete] = useState<boolean>();
|
||||
@ -46,8 +46,16 @@ function Setting() {
|
||||
|
||||
useEffect(() => {
|
||||
const loadConfigs = async () => {
|
||||
const [cloudSync, menuExpandNum, checkCycle, updateDisabled, silenceUpdate, eslintConfig, enableEslint] =
|
||||
await Promise.all([
|
||||
const [
|
||||
cloudSync,
|
||||
menuExpandNum,
|
||||
checkCycle,
|
||||
updateDisabled,
|
||||
silenceUpdate,
|
||||
eslintConfig,
|
||||
enableEslint,
|
||||
language,
|
||||
] = await Promise.all([
|
||||
systemConfig.getCloudSync(),
|
||||
systemConfig.getMenuExpandNum(),
|
||||
systemConfig.getCheckScriptUpdateCycle(),
|
||||
@ -55,6 +63,7 @@ function Setting() {
|
||||
systemConfig.getSilenceUpdateScript(),
|
||||
systemConfig.getEslintConfig(),
|
||||
systemConfig.getEnableEslint(),
|
||||
systemConfig.getLanguage(),
|
||||
]);
|
||||
|
||||
setSyncDelete(cloudSync.syncDelete);
|
||||
@ -67,6 +76,7 @@ function Setting() {
|
||||
setSilenceUpdateScript(silenceUpdate);
|
||||
setEslintConfig(eslintConfig);
|
||||
setEnableEslint(enableEslint);
|
||||
setLanguage(language);
|
||||
};
|
||||
|
||||
loadConfigs();
|
||||
@ -96,9 +106,7 @@ function Setting() {
|
||||
return;
|
||||
}
|
||||
setLanguage(value);
|
||||
i18n.changeLanguage(value);
|
||||
dayjs.locale(value.toLocaleLowerCase());
|
||||
localStorage.language = value;
|
||||
systemConfig.setLanguage(value);
|
||||
Message.success(t("language_change_tip")!);
|
||||
}}
|
||||
>
|
||||
|
@ -3,6 +3,8 @@ import ChromeStorage from "./chrome_storage";
|
||||
import { defaultConfig } from "../../../packages/eslint/linter-config";
|
||||
import { FileSystemType } from "@Packages/filesystem/factory";
|
||||
import { MessageQueue } from "@Packages/message/message_queue";
|
||||
import i18n from "@App/locales/locales";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
export const SystamConfigChange = "systemConfigChange";
|
||||
|
||||
@ -214,4 +216,26 @@ export class SystemConfig {
|
||||
setMenuExpandNum(val: number) {
|
||||
this.set("menu_expand_num", val);
|
||||
}
|
||||
|
||||
async getLanguage() {
|
||||
const defaultLanguage = await new Promise<string>((resolve) => {
|
||||
chrome.i18n.getAcceptLanguages((lngs) => {
|
||||
// 遍历数组寻找匹配语言
|
||||
for (let i = 0; i < lngs.length; i += 1) {
|
||||
const lng = lngs[i];
|
||||
if (i18n.hasResourceBundle(lng, "translation")) {
|
||||
resolve(lng);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
return this.get("language", defaultLanguage || chrome.i18n.getUILanguage());
|
||||
}
|
||||
|
||||
setLanguage(value: any) {
|
||||
this.set("language", value);
|
||||
i18n.changeLanguage(value);
|
||||
dayjs.locale(value.toLocaleLowerCase());
|
||||
}
|
||||
}
|
||||
|
@ -58,7 +58,8 @@ async function main() {
|
||||
// 初始化管理器
|
||||
const message = new ExtensionMessage(true);
|
||||
const server = new Server("serviceWorker", message);
|
||||
const manager = new ServiceWorkerManager(server, new MessageQueue(), new ServiceWorkerMessageSend());
|
||||
const messageQueue = new MessageQueue();
|
||||
const manager = new ServiceWorkerManager(server, messageQueue, new ServiceWorkerMessageSend());
|
||||
manager.initManager();
|
||||
// 初始化沙盒环境
|
||||
await setupOffscreenDocument();
|
||||
|
Loading…
x
Reference in New Issue
Block a user