Skip to content

Commit

Permalink
🐛 修复沙盒隔离问题 #189
Browse files Browse the repository at this point in the history
  • Loading branch information
CodFrm committed May 17, 2023
1 parent 4dba268 commit 647de2e
Show file tree
Hide file tree
Showing 8 changed files with 99 additions and 96 deletions.
61 changes: 34 additions & 27 deletions docs/架构设计.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
[TOC]

# 脚本猫架构设计
> 本文档用于记录脚本猫的主要架构设计

> 本文档用于记录脚本猫的主要架构设计
## 目录结构

Expand All @@ -18,26 +17,19 @@ TODO
- [scripts](../src/app/repo/script.ts) 用户脚本
- [subscribe](../src/app/repo/subscribe.ts) 订阅脚本



## 代码架构

扩展中有很多操作可能是在页面中,但是实际上的操作生效是需要在background进行,甚至是有更复杂的逻辑,例如需要广播,这时候需要对这些操作逻辑进行解耦。这里我们通过[通讯机制](./通讯机制.md)去做,使用通讯机制实现一个事件,在前台页面中调用,后台页面进行操作。我们将这些代码放在[src/app/service](../src/app/service)文件夹中,每一个服务分为三个文件:
扩展中有很多操作可能是在页面中,但是实际上的操作生效是需要在 background 进行,甚至是有更复杂的逻辑,例如需要广播,这时候需要对这些操作逻辑进行解耦。这里我们通过[通讯机制](./通讯机制.md)去做,使用通讯机制实现一个事件,在前台页面中调用,后台页面进行操作。我们将这些代码放在[src/app/service](../src/app/service)文件夹中,每一个服务分为三个文件:

- controller 控制器,用于前台页面发起操作请求,例如脚本的安装/脚本删除,也可以将页面相关的业务代码写在此处
- manager 管理器,后台业务代码处理,监听事件处理,例如监听打开`.user.js`页面,打开一个新页面匹配match注入脚本
- event 事件处理,依赖注入manager,例如收到脚本安装的事件后将脚本数据写入数据库、更新match缓存,主要是将事件代码与后台控制代码解耦


- manager 管理器,后台业务代码处理,监听事件处理,例如监听打开`.user.js`页面,打开一个新页面匹配 match 注入脚本
- event 事件处理,依赖注入 manager,例如收到脚本安装的事件后将脚本数据写入数据库、更新 match 缓存,主要是将事件代码与后台控制代码解耦

**hook**

> 取这个名字更多的是想和上面的event区分开来,另外hook也允许拦截操作
使用hook将各个操作进行解耦,例如脚本数据存储时就会涉及到:脚本执行、脚本同步、脚本状态变更通知前端页面。


> 取这个名字更多的是想和上面的 event 区分开来,另外 hook 也允许拦截操作
使用 hook 将各个操作进行解耦,例如脚本数据存储时就会涉及到:脚本执行、脚本同步、脚本状态变更通知前端页面。

## 脚本

Expand All @@ -47,21 +39,17 @@ TODO

#### 唯一性判断

唯一标志有3个:uuid、url、name+namespace。

唯一标志有 3 个:uuid、url、name+namespace。

### 定时脚本

#### 定时器

定时器使用crontab实现,增加了一个once的概念,实现x段时间内最多执行一次。定时器基于[`cron`](https://www.npmjs.com/package/cron)库实现



定时器使用 crontab 实现,增加了一个 once 的概念,实现 x 段时间内最多执行一次。定时器基于[`cron`](https://www.npmjs.com/package/cron)库实现

## 日志

为了记录扩展的运行状态与问题排查,需要实现一个日志组件,看了很多开源日志库,但大多数只适用于nodejs无法用于浏览器扩展。自己简单实现一个日志组件,实现以下功能 :
为了记录扩展的运行状态与问题排查,需要实现一个日志组件,看了很多开源日志库,但大多数只适用于 nodejs 无法用于浏览器扩展。自己简单实现一个日志组件,实现以下功能 :

- 日志分级:可以自行控制日志的输出级别,打印到控制台,记录到数据库
- 日志字段:日志可以标记字段,用字段来进行分类查询
Expand All @@ -78,7 +66,7 @@ TODO

**Write**

日志写入接口,内置了`DBWriter``MessageWriter`DBWriter用于indexedDB落库,MessageWriter用于content/sandbox页通过通讯机制去落库
日志写入接口,内置了`DBWriter``MessageWriter`DBWriter 用于 indexedDB 落库,MessageWriter 用于 content/sandbox 页通过通讯机制去落库

```ts
// 初始化日志组件
Expand All @@ -95,14 +83,33 @@ LoggerCore.getInstance().logger({ env: "background" }).info("background start");

为了兼容油猴脚本,必须引入测试,主要针对油猴运行时的沙盒进行测试,以保证每次修改不会破坏兼容性。使用[`jest`](https://jestjs.io/zh-Hans/)作为测试框架,编写时也需要考虑代码的一个可测试性。单元测试文件与代码放在同级目录下(\*.ts/\*.test.ts),不另外开`tests`文件夹。

## uuid 生成逻辑与脚本安装

## uuid生成逻辑与脚本安装
### uuid 生成逻辑

### uuid生成逻辑
随机生成uuid
随机生成 uuid

### 脚本安装
首先通过name+namespace搜索是否有同名脚本, 没有则生成随机uuid安装, 有则使用同名脚本的uuid进行安装

首先通过 name+namespace 搜索是否有同名脚本, 没有则生成随机 uuid 安装, 有则使用同名脚本的 uuid 进行安装

### 脚本更新
传递脚本uuid, 使用uuid进行更新

传递脚本 uuid, 使用 uuid 进行更新

## 沙盒隔离

创建一个沙盒,隔离页面与脚本的对象

### typeof function

#### 普通 function

例如:setTimeout、setInterval、fetch 等,需要将函数的 this 指向 window

#### Map、Function、Array 等

这些是拥有 new 和静态方法的对象

#### 处理方式

2 changes: 1 addition & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
/** @type {import('ts-jest/dist/types').JestConfigWithTsJest} */
module.exports = {
preset: "ts-jest",
testEnvironment: "jsdom",
Expand Down
2 changes: 1 addition & 1 deletion src/app/const.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export const ExtVersion = "0.12.0";
export const ExtVersion = "0.13.0-beta";

export const ExtServer = "https://ext.scriptcat.org/";

Expand Down
1 change: 0 additions & 1 deletion src/linter.worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ self.addEventListener("message", (event) => {
text: err.fix.text,
};
}
console.log(err);
return {
code: {
value: err.ruleId || "",
Expand Down
2 changes: 1 addition & 1 deletion src/manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"manifest_version": 2,
"name": "ScriptCat",
"version": "0.12.0",
"version": "0.13.0.1010",
"author": "CodFrm",
"description": "脚本猫,一个用户脚本管理器,支持后台脚本、定时脚本、页面脚本,可编写脚本每天帮你自动处理事务.",
"options_ui": {
Expand Down
4 changes: 0 additions & 4 deletions src/pkg/utils/monaco-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,6 @@ export default function registerEditor() {
const fix = eslintFix.get(
`${code}|${val.startLineNumber}|${val.endLineNumber}|${val.startColumn}|${val.endColumn}`
);
console.log(
fix,
`${code}|${val.startLineNumber}|${val.endLineNumber}|${val.startColumn}|${val.endColumn}`
);
if (fix) {
const edit: languages.IWorkspaceTextEdit = {
resource: model.uri,
Expand Down
86 changes: 51 additions & 35 deletions src/runtime/content/utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,57 @@
import { init, proxyContext } from "./utils";
import { init, proxyContext, writables } from "./utils";

describe('proxy context', () => {
const context: any = {};
const global: any = {
gbok: 'gbok',
onload: null,
eval: () => {
console.log('eval');
},
};
init.set('onload', true);
init.set('gbok', true);
const _this = proxyContext(global, context);
describe("proxy context", () => {
const context: any = {};
const global: any = {
gbok: "gbok",
onload: null,
eval: () => {
console.log("eval");
},
};
init.set("onload", true);
init.set("gbok", true);
const _this = proxyContext(global, context);

it('set contenxt', () => {
_this['md5'] = 'ok';
expect(_this['md5']).toEqual('ok');
expect(global['md5']).toEqual(undefined);
});
it("set contenxt", () => {
_this["md5"] = "ok";
expect(_this["md5"]).toEqual("ok");
expect(global["md5"]).toEqual(undefined);
});

it('set window null', () => {
_this['onload'] = 'ok';
expect(_this['onload']).toEqual('ok');
expect(context['onload']).toEqual(undefined);
expect(global['onload']).toEqual('ok');
});
it("set window null", () => {
_this["onload"] = "ok";
expect(_this["onload"]).toEqual("ok");
expect(context["onload"]).toEqual(undefined);
expect(global["onload"]).toEqual("ok");
});

it('update', () => {
_this['okk'] = 'ok';
expect(_this['okk']).toEqual('ok');
expect(global['okk']).toEqual(undefined);
_this['okk'] = 'ok2';
expect(_this['okk']).toEqual('ok2');
expect(global['okk']).toEqual(undefined);
});
it("update", () => {
_this["okk"] = "ok";
expect(_this["okk"]).toEqual("ok");
expect(global["okk"]).toEqual(undefined);
_this["okk"] = "ok2";
expect(_this["okk"]).toEqual("ok2");
expect(global["okk"]).toEqual(undefined);
});

it('访问global的对象', () => {
expect(_this['gbok']).toEqual('gbok');
});
it("访问global的对象", () => {
expect(_this["gbok"]).toEqual("gbok");
});
});

describe("兼容问题", () => {
console.log("ok");
const _this = proxyContext({}, {});
// https://github.com/xcanwin/KeepChatGPT 环境隔离得不够干净导致的
it("Uncaught TypeError: Illegal invocation #189", () => {
return new Promise((resolve) => {
console.log(_this.setTimeout.prototype);
_this.setTimeout(resolve, 100);
});
});
// AC-baidu-重定向优化百度搜狗谷歌必应搜索_favicon_双列
it("TypeError: Object.freeze is not a function #116", () => {
expect(() => _this.Object.freeze({})).not.toThrow();
});
});
37 changes: 11 additions & 26 deletions src/runtime/content/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export function createContext(
return <GMApi>context;
}

const writables: { [key: string]: any } = {
export const writables: { [key: string]: any } = {
addEventListener: global.addEventListener,
removeEventListener: global.removeEventListener,
dispatchEvent: global.dispatchEvent,
Expand All @@ -125,7 +125,16 @@ const descs = Object.getOwnPropertyDescriptors(global);
Object.keys(descs).forEach((key) => {
const desc = descs[key];
if (desc && desc.writable && !writables[key]) {
writables[key] = desc.value;
if (typeof desc.value === "function") {
// 判断是否需要bind,例如Object、Function这些就不需要bind
if (desc.value.prototype) {
writables[key] = desc.value;
} else {
writables[key] = desc.value.bind(global);
}
} else {
writables[key] = desc.value;
}
} else {
init.set(key, true);
}
Expand Down Expand Up @@ -185,21 +194,9 @@ export function proxyContext(global: any, context: any) {
return context[name];
}
if (special[name] !== undefined) {
if (
typeof special[name] === "function" &&
!(<{ prototype: any }>special[name]).prototype
) {
return (<{ bind: any }>special[name]).bind(global);
}
return special[name];
}
if (global[name] !== undefined) {
if (
typeof global[name] === "function" &&
!(<{ prototype: any }>global[name]).prototype
) {
return (<{ bind: any }>global[name]).bind(global);
}
return global[name];
}
}
Expand Down Expand Up @@ -231,21 +228,9 @@ export function proxyContext(global: any, context: any) {
return true;
}
if (special[name] !== undefined) {
if (
typeof special[name] === "function" &&
!(<{ prototype: any }>special[name]).prototype
) {
return true;
}
return true;
}
if (global[name] !== undefined) {
if (
typeof global[name] === "function" &&
!(<{ prototype: any }>global[name]).prototype
) {
return true;
}
return true;
}
}
Expand Down

0 comments on commit 647de2e

Please sign in to comment.