Skip to content

Commit c5ac3e3

Browse files
cyfung1031Copilot
andauthored
⚡️ 强化错误检查,加入自定义esline-rules (#542)
* 强化错误检查,加入自定义esline-rules * 一个调试日志 * Update src/pkg/utils/utils.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * resolve("undefind"); * https://*/* --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 0341d3c commit c5ac3e3

File tree

22 files changed

+540
-110
lines changed

22 files changed

+540
-110
lines changed
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
export default {
2+
meta: {
3+
type: "problem",
4+
docs: {
5+
description: "Ensure callbacks check chrome.runtime.lastError appropriately",
6+
},
7+
schema: [],
8+
messages: {
9+
missing: "Callback for '{{api}}' lacks chrome.runtime.lastError check (should handle errors).",
10+
uncertain: "Callback for '{{api}}' may require lastError check—consider adding one.",
11+
},
12+
},
13+
create(context) {
14+
// APIs that must check lastError
15+
const mustCheck = new Set([
16+
"chrome.runtime.sendMessage",
17+
"chrome.runtime.connect",
18+
"chrome.runtime.getBackgroundPage",
19+
"chrome.runtime.getPackageDirectoryEntry",
20+
"chrome.permissions.contains",
21+
"chrome.permissions.request",
22+
"chrome.permissions.remove",
23+
"chrome.storage.get",
24+
"chrome.storage.set",
25+
"chrome.storage.remove",
26+
"chrome.storage.clear",
27+
"chrome.tabs.query",
28+
"chrome.tabs.create",
29+
"chrome.tabs.update",
30+
"chrome.tabs.remove",
31+
"chrome.tabs.sendMessage",
32+
"chrome.tabs.executeScript",
33+
"chrome.scripting.executeScript",
34+
"chrome.scripting.insertCSS",
35+
"chrome.scripting.removeCSS",
36+
"chrome.windows.create",
37+
"chrome.windows.get",
38+
"chrome.windows.update",
39+
"chrome.windows.remove",
40+
"chrome.notifications.create",
41+
"chrome.notifications.clear",
42+
"chrome.notifications.getAll",
43+
]);
44+
45+
// APIs that *might* require checking lastError (warning only)
46+
const maybeCheck = [
47+
"chrome.alarms.",
48+
"chrome.bookmarks.",
49+
"chrome.history.",
50+
"chrome.cookies.",
51+
"chrome.webNavigation.",
52+
"chrome.webRequest.",
53+
"chrome.declarativeNetRequest.",
54+
"chrome.management.",
55+
];
56+
57+
function isMaybeCheck(callee) {
58+
return maybeCheck.some((prefix) => callee.startsWith(prefix));
59+
}
60+
61+
return {
62+
CallExpression(node) {
63+
const cb = node.arguments.find(
64+
(a) => a && (a.type === "FunctionExpression" || a.type === "ArrowFunctionExpression")
65+
);
66+
if (!cb) return;
67+
68+
const callee = context.getSourceCode().getText(node.callee);
69+
if (!callee.startsWith("chrome.")) return;
70+
71+
// Skip Promise-based API usage
72+
const isPromise = node.parent?.type === "AwaitExpression";
73+
if (isPromise) return;
74+
75+
// Check callback body for lastError reference
76+
const cbBody = context.getSourceCode().getText(cb.body);
77+
const checksLastError = cbBody.includes("chrome.runtime.lastError");
78+
79+
if (mustCheck.has(callee)) {
80+
if (!checksLastError) {
81+
context.report({
82+
node: cb,
83+
messageId: "missing",
84+
data: { api: callee },
85+
});
86+
}
87+
} else if (isMaybeCheck(callee)) {
88+
if (!checksLastError) {
89+
context.report({
90+
node: cb,
91+
messageId: "uncertain",
92+
data: { api: callee },
93+
});
94+
}
95+
}
96+
},
97+
};
98+
},
99+
};

eslint.config.mjs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import reactJsx from "eslint-plugin-react/configs/jsx-runtime.js";
77
import react from "eslint-plugin-react/configs/recommended.js";
88
import ts from "typescript-eslint";
99
import globals from "globals";
10+
import requireLastErrorCheck from "./eslint-rules/require-last-error-check.js";
1011

1112
export default [
1213
{
@@ -34,6 +35,11 @@ export default [
3435
plugins: {
3536
"react-hooks": reactHooks,
3637
prettier: prettierPlugin,
38+
"chrome-error": {
39+
rules: {
40+
"require-last-error-check": requireLastErrorCheck,
41+
},
42+
},
3743
},
3844
rules: {
3945
"@typescript-eslint/no-explicit-any": "off",
@@ -52,6 +58,7 @@ export default [
5258
"react-hooks/exhaustive-deps": "off",
5359
"prettier/prettier": "error",
5460
"react/prop-types": "off",
61+
"chrome-error/require-last-error-check": "error",
5562
},
5663
},
5764
prettier,

packages/cloudscript/cloudscript.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ export default interface CloudScript {
2424
function getCookies(detail: chrome.cookies.GetAllDetails): Promise<chrome.cookies.Cookie[]> {
2525
return new Promise((resolve) => {
2626
chrome.cookies.getAll(detail, (cookies) => {
27+
const lastError = chrome.runtime.lastError;
28+
if (lastError) {
29+
console.error("chrome.runtime.lastError in chrome.cookies.getAll:", lastError);
30+
// 无视错误继续执行
31+
}
2732
resolve(cookies);
2833
});
2934
});

packages/filesystem/auth.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@ export function NetDisk(netDiskType: NetDiskType) {
5353
url: `${ExtServer}api/v1/auth/net-disk?netDiskType=${netDiskType}`,
5454
})
5555
.then(({ id: tabId }) => {
56+
const lastError = chrome.runtime.lastError;
57+
if (lastError) {
58+
console.error("chrome.runtime.lastError in chrome.tabs.create:", lastError);
59+
// 没有 tabId 无法执行
60+
return;
61+
}
5662
const t = setInterval(async () => {
5763
try {
5864
const tab = await chrome.tabs.get(tabId!);

packages/message/custom_event_message.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ export class CustomEventMessage implements Message {
7878
data,
7979
};
8080
this.nativeSend(body);
81+
// EventEmitter3 採用同步事件设计,callback会被马上执行而不像传统javascript架构以下一个macrotask 执行
8182
resolve(new WindowMessageConnect(body.messageId, this.EE, new CustomEventPostMessage(this)));
8283
});
8384
}
@@ -97,21 +98,28 @@ export class CustomEventMessage implements Message {
9798
detail,
9899
});
99100
window.dispatchEvent(ev);
101+
// EventEmitter3 採用同步事件设计,callback会被马上执行而不像传统javascript架构以下一个macrotask 执行
100102
}
101103

102104
sendMessage(data: any): Promise<any> {
103-
return new Promise((resolve) => {
105+
return new Promise((resolve: ((value: any) => void) | null) => {
104106
const body: WindowMessageBody = {
105107
messageId: uuidv4(),
106108
type: "sendMessage",
107109
data,
108110
};
109-
const callback = (body: WindowMessageBody) => {
110-
this.EE.removeListener("response:" + body.messageId, callback);
111-
resolve(body.data);
111+
let callback: EventEmitter.EventListener<string | symbol, any> | null = (body: WindowMessageBody) => {
112+
if (callback !== null) {
113+
this.EE.removeListener("response:" + body.messageId, callback);
114+
resolve!(body.data);
115+
callback = null; // 设为 null 提醒JS引擎可以GC
116+
resolve = null;
117+
}
112118
};
113119
this.EE.addListener("response:" + body.messageId, callback);
114120
this.nativeSend(body);
121+
// EventEmitter3 採用同步事件设计,callback会被马上执行而不像传统javascript架构以下一个macrotask 执行
122+
callback = null;
115123
});
116124
}
117125

@@ -125,12 +133,17 @@ export class CustomEventMessage implements Message {
125133
data,
126134
};
127135
let ret: any;
128-
const callback = (body: WindowMessageBody) => {
129-
this.EE.removeListener("response:" + body.messageId, callback);
130-
ret = body.data;
136+
let callback: EventEmitter.EventListener<string | symbol, any> | null = (body: WindowMessageBody) => {
137+
if (callback !== null) {
138+
this.EE.removeListener("response:" + body.messageId, callback);
139+
ret = body.data;
140+
callback = null; // 设为 null 提醒JS引擎可以GC
141+
}
131142
};
132143
this.EE.addListener("response:" + body.messageId, callback);
133144
this.nativeSend(body);
145+
// EventEmitter3 採用同步事件设计,callback会被马上执行而不像传统javascript架构以下一个macrotask 执行
146+
callback = null;
134147
return ret;
135148
}
136149

packages/message/extension_message.ts

Lines changed: 64 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,17 @@ export class ExtensionMessageSend implements MessageSend {
1313

1414
// 发送消息 注意不进行回调的内存泄漏
1515
sendMessage(data: any): Promise<any> {
16-
return new Promise((resolve) => {
16+
return new Promise((resolve: ((value: any) => void) | null) => {
1717
chrome.runtime.sendMessage(data, (resp) => {
18-
resolve(resp);
18+
const lastError = chrome.runtime.lastError;
19+
if (lastError) {
20+
console.error("chrome.runtime.lastError in chrome.runtime.sendMessage:", lastError);
21+
// 通信API出错不回继续对话
22+
resolve = null;
23+
return;
24+
}
25+
resolve!(resp);
26+
resolve = null;
1927
});
2028
});
2129
}
@@ -25,6 +33,11 @@ export class ExtensionMessageSend implements MessageSend {
2533
export class ServiceWorkerMessage extends ExtensionMessageSend implements Message {
2634
onConnect(callback: (data: any, con: MessageConnect) => void): void {
2735
chrome.runtime.onConnect.addListener((port) => {
36+
const lastError = chrome.runtime.lastError;
37+
if (lastError) {
38+
console.error("chrome.runtime.lastError in chrome.runtime.onConnect", lastError);
39+
// 消息API发生错误因此不继续执行
40+
}
2841
const handler = (msg: any) => {
2942
port.onMessage.removeListener(handler);
3043
callback(msg, new ExtensionMessageConnect(port));
@@ -35,6 +48,12 @@ export class ServiceWorkerMessage extends ExtensionMessageSend implements Messag
3548

3649
onMessage(callback: (data: any, sendResponse: (data: any) => void, sender: MessageSender) => void): void {
3750
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
51+
const lastError = chrome.runtime.lastError;
52+
if (lastError) {
53+
console.error("chrome.runtime.lastError in chrome.runtime.onMessage:", lastError);
54+
// 消息API发生错误因此不继续执行
55+
return false;
56+
}
3857
if (msg.action === "messageQueue") {
3958
return false;
4059
}
@@ -49,37 +68,60 @@ export class ExtensionMessage extends ExtensionMessageSend implements Message {
4968
}
5069

5170
onConnect(callback: (data: any, con: MessageConnect) => void) {
52-
chrome.runtime.onConnect.addListener((port) => {
71+
chrome.runtime.onConnect.addListener((port: chrome.runtime.Port | null) => {
72+
const lastError = chrome.runtime.lastError;
73+
if (lastError) {
74+
console.error("chrome.runtime.lastError in chrome.runtime.onConnect", lastError);
75+
// 消息API发生错误因此不继续执行
76+
}
5377
const handler = (msg: any) => {
54-
port.onMessage.removeListener(handler);
55-
callback(msg, new ExtensionMessageConnect(port));
78+
port!.onMessage.removeListener(handler);
79+
callback(msg, new ExtensionMessageConnect(port!));
80+
port = null;
5681
};
57-
port.onMessage.addListener(handler);
82+
port!.onMessage.addListener(handler);
5883
});
5984

6085
if (this.onUserScript) {
6186
// 监听用户脚本的连接
62-
chrome.runtime.onUserScriptConnect.addListener((port) => {
87+
chrome.runtime.onUserScriptConnect.addListener((port: chrome.runtime.Port | null) => {
88+
const lastError = chrome.runtime.lastError;
89+
if (lastError) {
90+
console.error("chrome.runtime.lastError in chrome.runtime.onUserScriptConnect:", lastError);
91+
}
6392
const handler = (msg: any) => {
64-
port.onMessage.removeListener(handler);
65-
callback(msg, new ExtensionMessageConnect(port));
93+
port!.onMessage.removeListener(handler);
94+
callback(msg, new ExtensionMessageConnect(port!));
95+
port = null;
6696
};
67-
port.onMessage.addListener(handler);
97+
port!.onMessage.addListener(handler);
6898
});
6999
}
70100
}
71101

72102
// 注意chrome.runtime.onMessage.addListener的回调函数需要返回true才能处理异步请求
73103
onMessage(callback: (data: any, sendResponse: (data: any) => void, sender: MessageSender) => void): void {
74-
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
104+
chrome.runtime.onMessage?.addListener((msg, sender, sendResponse) => {
105+
const lastError = chrome.runtime.lastError;
106+
if (lastError) {
107+
console.error("chrome.runtime.lastError in chrome.runtime.onMessage:", lastError);
108+
// 消息API发生错误因此不继续执行
109+
return false;
110+
}
75111
if (msg.action === "messageQueue") {
76112
return false;
77113
}
78114
return callback(msg, sendResponse, sender);
79115
});
80116
if (this.onUserScript) {
81117
// 监听用户脚本的消息
82-
chrome.runtime.onUserScriptMessage.addListener((msg, sender, sendResponse) => {
118+
chrome.runtime.onUserScriptMessage?.addListener((msg, sender, sendResponse) => {
119+
const lastError = chrome.runtime.lastError;
120+
if (lastError) {
121+
console.error("chrome.runtime.lastError in chrome.runtime.onUserScriptMessage:", lastError);
122+
// 消息API发生错误因此不继续执行
123+
return false;
124+
}
83125
if (msg.action === "messageQueue") {
84126
return false;
85127
}
@@ -125,10 +167,20 @@ export class ExtensionContentMessageSend extends ExtensionMessageSend {
125167
if (!this.options?.documentId || this.options?.frameId) {
126168
// 发送给指定的tab
127169
chrome.tabs.sendMessage(this.tabId, data, (resp) => {
170+
const lastError = chrome.runtime.lastError;
171+
if (lastError) {
172+
console.error("chrome.runtime.lastError in chrome.tabs.sendMessage:", lastError);
173+
// 无视错误继续执行
174+
}
128175
resolve(resp);
129176
});
130177
} else {
131178
chrome.tabs.sendMessage(this.tabId, data, this.options, (resp) => {
179+
const lastError = chrome.runtime.lastError;
180+
if (lastError) {
181+
console.error("chrome.runtime.lastError in chrome.tabs.sendMessage:", lastError);
182+
// 无视错误继续执行
183+
}
132184
resolve(resp);
133185
});
134186
}

packages/message/message_queue.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ export class MessageQueue {
1111

1212
constructor() {
1313
chrome.runtime.onMessage.addListener((msg) => {
14+
const lastError = chrome.runtime.lastError;
15+
if (lastError) {
16+
console.error("chrome.runtime.lastError in chrome.runtime.onMessage:", lastError);
17+
// 消息API发生错误因此不继续执行
18+
return false;
19+
}
1420
if (msg.action === "messageQueue") {
1521
this.handler(msg.data);
1622
}

packages/message/server.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,15 @@ export class Server {
3636
message: Message,
3737
private enableConnect: boolean = true
3838
) {
39-
this.enableConnect &&
39+
if (this.enableConnect) {
4040
message.onConnect((msg: any, con: MessageConnect) => {
4141
this.logger.trace("server onConnect", { msg });
4242
if (msg.action.startsWith(prefix)) {
4343
return this.connectHandle(msg.action.slice(prefix.length + 1), msg.data, con);
4444
}
4545
return false;
4646
});
47+
}
4748

4849
message.onMessage((msg: { action: string; data: any }, sendResponse, sender) => {
4950
this.logger.trace("server onMessage", { msg: msg as any });

0 commit comments

Comments
 (0)