Skip to content

Commit da16ddf

Browse files
cyfung1031CodFrmCopilot
authored
⚡️ 更新 Subscribe 相关代码和追加注释 (#1198)
* 修正 Subscribe 的 Script 的 静默更新 * lint * 更新 Subscribe 相关代码和追加注释 * 🐛 修正订阅脚本的静默更新与 connect 权限逻辑 根据文档设计,订阅脚本应始终静默更新,不受「非重要变更静默更新脚本」 开关控制;订阅下脚本的 connect 权限完全由订阅的 connect 覆盖。 - subscribe.ts: 订阅本身更新去掉 toggle 依赖,始终静默(除非 connect 变化) - script.ts: 订阅下脚本更新始终静默,不检查 toggle 和 connect - gm_api.ts: 运行时 GM_xmlhttpRequest/GM_cookie 的 connect 权限使用订阅声明的覆盖 - utils.ts: 简化 checkSilenceUpdate,移除不再需要的 subscribeMetadata 参数 * 🐛 修复 Subscribe 脚本关联清理和状态类型问题 - 修复删除分支未清理 subscribe.scripts 关联导致每次更新重复触发的 bug - 删除未使用的 unknown3/unknown4 状态值 - 优化 upsertScript 中已安装脚本查找,用 Map 索引替代逐个全表扫描 * Update src/app/service/service_worker/subscribe.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: 王一之 <yz@ggnb.top> Co-authored-by: wangyizhi <i@xloli.top> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent a3c2581 commit da16ddf

7 files changed

Lines changed: 132 additions & 74 deletions

File tree

src/app/repo/subscribe.ts

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,26 @@ import type { SCMetadata } from "./metadata";
33

44
export { SCMetadata };
55

6-
export type SUBSCRIBE_STATUS = 1 | 2 | 3 | 4;
7-
export const SUBSCRIBE_STATUS_ENABLE: SUBSCRIBE_STATUS = 1;
8-
export const SUBSCRIBE_STATUS_DISABLE: SUBSCRIBE_STATUS = 2;
6+
export const SubscribeStatusType = {
7+
enable: 1,
8+
disable: 2,
9+
} as const;
10+
11+
export type SubscribeStatusType = ValueOf<typeof SubscribeStatusType>;
912

1013
export interface SubscribeScript {
1114
uuid: string;
12-
url: string;
15+
url: string; // url of the user.js
1316
}
1417

1518
export interface Subscribe {
16-
url: string;
19+
url: string; // url of the user.sub.js; 作为唯一键。暂时只支持网址。( 如需要支持 手动生成 Subscribe,日后可升级成 url / uuid )
1720
name: string;
18-
code: string;
21+
code: string; // (meta) code of the user.sub.js
1922
author: string;
20-
scripts: { [key: string]: SubscribeScript };
23+
scripts: Record<string, SubscribeScript>; // 这里只储存脚本的 uuid 和 url 等资讯,而不是实际的代码
2124
metadata: SCMetadata;
22-
status: SUBSCRIBE_STATUS;
25+
status: SubscribeStatusType;
2326
createtime: number;
2427
updatetime?: number;
2528
checktime: number;
@@ -30,10 +33,6 @@ export class SubscribeDAO extends Repo<Subscribe> {
3033
super("subscribe");
3134
}
3235

33-
public findByUrl(url: string) {
34-
return this.get(url);
35-
}
36-
3736
public save(val: Subscribe) {
3837
return super._save(val.url, val);
3938
}

src/app/service/service_worker/gm_api/gm_api_subscribe.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { describe, it, expect, beforeEach } from "vitest";
22
import { ScriptDAO, SCRIPT_TYPE_NORMAL, SCRIPT_STATUS_ENABLE, SCRIPT_RUN_STATUS_COMPLETE } from "@App/app/repo/scripts";
33
import type { Script } from "@App/app/repo/scripts";
4-
import { SubscribeDAO, SUBSCRIBE_STATUS_ENABLE } from "@App/app/repo/subscribe";
4+
import { SubscribeDAO, SubscribeStatusType } from "@App/app/repo/subscribe";
55
import type { Subscribe } from "@App/app/repo/subscribe";
66
import type { SCMetadata } from "@App/app/repo/metadata";
77
import GMApi, { MockGMExternalDependencies } from "./gm_api";
@@ -43,7 +43,7 @@ function makeSubscribe(url: string, connect?: string[]): Subscribe {
4343
author: "test",
4444
scripts: {},
4545
metadata,
46-
status: SUBSCRIBE_STATUS_ENABLE,
46+
status: SubscribeStatusType.enable,
4747
createtime: Date.now(),
4848
checktime: Date.now(),
4949
};

src/app/service/service_worker/subscribe.ts

Lines changed: 95 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import LoggerCore from "@App/app/logger/core";
22
import Logger from "@App/app/logger/logger";
3-
import { ScriptDAO } from "@App/app/repo/scripts";
3+
import { ScriptDAO, type Script } from "@App/app/repo/scripts";
44
import type { SCMetadata, Subscribe, SubscribeScript } from "@App/app/repo/subscribe";
5-
import { SUBSCRIBE_STATUS_DISABLE, SUBSCRIBE_STATUS_ENABLE, SubscribeDAO } from "@App/app/repo/subscribe";
5+
import { SubscribeDAO, SubscribeStatusType } from "@App/app/repo/subscribe";
66
import { type IMessageQueue } from "@Packages/message/message_queue";
77
import { type Group } from "@Packages/message/server";
88
import { type ScriptService } from "./script";
@@ -31,13 +31,17 @@ export class SubscribeService {
3131
}
3232

3333
async install(param: { subscribe: Subscribe }) {
34+
// 1)由安装页呼叫,进行 user.sub.js 的安装
35+
// 2)静默更新启动状态下,Subscribe 列表自动更新
3436
const logger = this.logger.with({
3537
subscribeUrl: param.subscribe.url,
3638
name: param.subscribe.name,
3739
});
3840
try {
39-
await this.subscribeDAO.save(param.subscribe);
41+
await this.subscribeDAO.save(param.subscribe); // 所谓的安装,仅储存脚本资源。
4042
logger.info("upsert subscribe success");
43+
// 广播后才会根据 subscribe.scripts 的 url 取得/更新脚本
44+
// 注:installSubscribe 的广播是自己和自己对话。(不等待回应)
4145
this.mq.publish<TInstallSubscribe>("installSubscribe", {
4246
subscribe: param.subscribe,
4347
});
@@ -83,85 +87,121 @@ export class SubscribeService {
8387
}
8488
}
8589

86-
// 更新订阅的脚本
90+
// 更新订阅的脚本( installSubscribe )
91+
// 已订阅的脚本则根据 Script脚本 本身的更新逻辑更新,与 Subscribe脚本 的更新无关
8792
async upsertScript(url: string) {
8893
const subscribe = await this.subscribeDAO.get(url);
89-
if (!subscribe) return;
94+
if (!subscribe || !subscribe.metadata.usersubscribe) return; // 有效的 Subscribe 必定有 usersubscribe
9095
const logger = this.logger.with({
9196
url: subscribe.url,
9297
name: subscribe.name,
9398
});
9499
// 对比脚本是否有变化
95-
const addScript: string[] = [];
96-
const removeScript: SubscribeScript[] = [];
97-
const scriptUrl = subscribe.metadata.scripturl || [];
98-
const scripts = Object.keys(subscribe.scripts);
99-
for (const url of scriptUrl) {
100+
const addedScripts: string[] = [];
101+
const removedScripts: SubscribeScript[] = [];
102+
const metaScriptUrlSet = new Set(subscribe.metadata.scripturl || []); // 订阅列表
103+
const subscribeScripts = new Set(Object.keys(subscribe.scripts)); // 已关联 uuid 的列表
104+
// 注:首次安装时, subscribeScripts 是空的。
105+
for (const url of metaScriptUrlSet) {
100106
// 不存在于已安装的脚本中, 则添加
101-
if (!scripts.includes(url)) {
102-
addScript.push(url);
107+
if (!subscribeScripts.has(url)) {
108+
addedScripts.push(url);
103109
}
104110
}
105-
for (const url of scripts) {
111+
for (const url of subscribeScripts) {
106112
// 不存在于订阅的脚本中, 则删除
107-
if (!scriptUrl.includes(url)) {
108-
removeScript.push(subscribe.scripts[url]);
113+
if (!metaScriptUrlSet.has(url)) {
114+
removedScripts.push(subscribe.scripts[url]);
109115
}
110116
}
111117

112-
const notification: string[][] = [[], []];
113-
const result: Promise<boolean>[] = [];
114-
// 添加脚本
115-
addScript.forEach((url) => {
116-
result.push(
118+
// 一次性取出所有已安装脚本,建立 URL → Script 的索引,避免每个 addedScript 都全表扫描
119+
const allScripts = await this.scriptDAO.find();
120+
const scriptByUrl = new Map<string, Script>();
121+
for (const script of allScripts) {
122+
if (script.downloadUrl) scriptByUrl.set(script.downloadUrl, script);
123+
if (script.origin) scriptByUrl.set(script.origin, script);
124+
}
125+
126+
const addedScriptNames: string[] = [];
127+
const removedScriptNames: string[] = [];
128+
const promises: Promise<void>[] = [];
129+
// 添加脚本: 根据 订阅列表 的 Script脚本URLs 进行安装
130+
addedScripts.forEach((url) => {
131+
promises.push(
117132
(async () => {
118-
const script = await this.scriptService.installByUrl(url, "subscribe", subscribe.url);
119-
subscribe.scripts[url] = {
120-
url,
121-
uuid: script.uuid,
122-
};
123-
notification[0].push(i18nName(script));
124-
return true;
133+
const existingScript = scriptByUrl.get(url);
134+
if (existingScript) {
135+
// 仅关联至 已安装脚本的 uuid
136+
// 注:1)已安装的脚本可能是用户用直接下载方式安装
137+
// 2)已安装的脚本可能是用户用其他 Subscribe 安装
138+
// 这里的 existingScript 的 subscribeUrl 值不一定是这个 Subscribe 的 url
139+
subscribe.scripts[url] = {
140+
url,
141+
uuid: existingScript.uuid,
142+
};
143+
} else {
144+
// 安装Script脚本 ( script.subscribeUrl 会指定为这个 Subscribe. 当移除 Subscribe 时会一并移除 )
145+
const script = await this.scriptService.installByUrl(url, "subscribe", subscribe.url);
146+
const name = i18nName(script);
147+
// 把Script脚本关联至Subscribe
148+
subscribe.scripts[url] = {
149+
url,
150+
uuid: script.uuid,
151+
};
152+
addedScriptNames.push(name);
153+
}
125154
})().catch((e) => {
126155
logger.error("install script failed", Logger.E(e));
127-
return false;
128156
})
129157
);
130158
});
131-
// 删除脚本
132-
removeScript.forEach((item) => {
159+
// 删除脚本: 根据 subscribeScripts 的 Script脚本UUIDs 进行反安装
160+
removedScripts.forEach((item) => {
133161
// 通过uuid查询脚本id
134-
result.push(
162+
promises.push(
135163
(async () => {
136-
const script = await this.scriptDAO.findByUUID(item.uuid);
164+
// 以 uuid 找出已安装的Script脚本资讯
165+
const script = await this.scriptDAO.get(item.uuid);
166+
const url = item.url;
167+
// 无论是否删除脚本,都需要清理 subscribe.scripts 中的关联
168+
delete subscribe.scripts[url];
137169
if (script) {
138-
notification[1].push(i18nName(script));
139-
// 删除脚本
140-
this.scriptService.deleteScript(script.uuid);
170+
const name = i18nName(script);
171+
// 如果不是以此 Subscribe 安装的话则略过删除(例如其他 Subscribe、直接安装、本地安装等)
172+
if (script.subscribeUrl === subscribe.url) {
173+
await this.scriptService.deleteScript(script.uuid);
174+
removedScriptNames.push(name);
175+
} else {
176+
logger.warn("Subscribe Update: skip deletion", {
177+
scriptUUID: script.uuid,
178+
scriptUrl: url,
179+
scriptName: name,
180+
});
181+
}
141182
}
142-
return true;
143183
})().catch((e) => {
144184
logger.error("delete script failed", Logger.E(e));
145-
return false;
146185
})
147186
);
148187
});
149188

150-
await Promise.allSettled(result);
189+
await Promise.allSettled(promises);
151190

191+
// 把 subscribe.scripts 的新资讯储存到 subscribeDAO
152192
await this.subscribeDAO.update(subscribe.url, subscribe);
153193

154194
InfoNotification(
155195
i18n.t("notification.subscribe_update", { subscribeName: subscribe.name }),
156196
i18n.t("notification.subscribe_update_desc", {
157-
newScripts: notification[0].join(","),
158-
deletedScripts: notification[1].join(","),
197+
newScripts: addedScriptNames.join(","),
198+
deletedScripts: removedScriptNames.join(","),
159199
})
160200
);
161201

162-
logger.info("subscribe update", {
163-
install: notification[0],
164-
update: notification[1],
202+
logger.info("subscribe list update", {
203+
installed: addedScriptNames,
204+
deleted: removedScriptNames,
165205
});
166206

167207
return true;
@@ -183,9 +223,9 @@ export class SubscribeService {
183223
});
184224
try {
185225
if (delayFn) await delayFn();
186-
const code = await fetchScriptBody(url);
187-
const metadata = parseMetadata(code);
188-
if (!metadata) {
226+
const code = await fetchScriptBody(url); // user.sub.js 的 代码
227+
const metadata = parseMetadata(code); // user.sub.js 的 metadata = 代码内容分析; metadata.usersubscribe 是 空阵列
228+
if (!metadata || !metadata.usersubscribe) {
189229
logger.error("parse metadata failed");
190230
return false;
191231
}
@@ -203,11 +243,17 @@ export class SubscribeService {
203243
}
204244

205245
// 检查更新
246+
/**
247+
* @param url Subscribe脚本 的 url
248+
* @param source 系统自动检查: "system"; subscribeClient.checkUpdate(subscribe.url) 的时候: "user"
249+
* @returns
250+
*/
206251
async checkUpdate(url: string, source: InstallSource) {
207252
const subscribe = await this.subscribeDAO.get(url);
208253
if (!subscribe) {
209254
return false;
210255
}
256+
// 先写入更新触发时间
211257
await this.subscribeDAO.update(url, { checktime: Date.now() });
212258
const logger = this.logger.with({
213259
url: subscribe.url,
@@ -263,7 +309,8 @@ export class SubscribeService {
263309
});
264310

265311
for (const subscribe of list) {
266-
if (!checkDisable && subscribe.status === SUBSCRIBE_STATUS_ENABLE) {
312+
if (!checkDisable && subscribe.status === SubscribeStatusType.disable) {
313+
// 旧代码一直写反了这个 enable disable
267314
continue;
268315
}
269316
this.checkUpdate(subscribe.url, "system");
@@ -280,7 +327,7 @@ export class SubscribeService {
280327
});
281328
try {
282329
await this.subscribeDAO.update(param.url, {
283-
status: param.enable ? SUBSCRIBE_STATUS_ENABLE : SUBSCRIBE_STATUS_DISABLE,
330+
status: param.enable ? SubscribeStatusType.enable : SubscribeStatusType.disable,
284331
});
285332
logger.info("enable subscribe success");
286333
return true;

src/pages/install/App.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -474,7 +474,7 @@ function App() {
474474

475475
try {
476476
if (scriptInfo?.userSubscribe) {
477-
await subscribeClient.install(upsertScript as Subscribe);
477+
await subscribeClient.install(upsertScript as Subscribe); // 首次安装时,upsertScript 里的 scripts 为空物件
478478
Message.success(t("subscribe_success")!);
479479
setBtnText(t("subscribe_success")!);
480480
} else {

src/pages/options/routes/SubscribeList.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
Typography,
1313
} from "@arco-design/web-react";
1414
import type { Subscribe } from "@App/app/repo/subscribe";
15-
import { SUBSCRIBE_STATUS_DISABLE, SUBSCRIBE_STATUS_ENABLE, SubscribeDAO } from "@App/app/repo/subscribe";
15+
import { SubscribeStatusType, SubscribeDAO } from "@App/app/repo/subscribe";
1616
import type { ColumnProps } from "@arco-design/web-react/es/Table";
1717
import { IconSearch, IconUserAdd } from "@arco-design/web-react/icon";
1818
import { semTime } from "@App/pkg/utils/dayjs";
@@ -69,18 +69,18 @@ function SubscribeList() {
6969
filters: [
7070
{
7171
text: t("enable"),
72-
value: SUBSCRIBE_STATUS_ENABLE,
72+
value: SubscribeStatusType.enable,
7373
},
7474
{
7575
text: t("disable"),
76-
value: SUBSCRIBE_STATUS_DISABLE,
76+
value: SubscribeStatusType.disable,
7777
},
7878
],
7979
onFilter: (value, row) => row.status === value,
8080
render: (col, item: ListType, index) => {
8181
return (
8282
<Switch
83-
checked={item.status === SUBSCRIBE_STATUS_ENABLE}
83+
checked={item.status === SubscribeStatusType.enable}
8484
loading={item.loading}
8585
disabled={item.loading}
8686
onChange={(checked) => {
@@ -98,7 +98,7 @@ function SubscribeList() {
9898
setListEntry(index, {
9999
loading: false,
100100
...(statusChange && {
101-
status: checked ? SUBSCRIBE_STATUS_ENABLE : SUBSCRIBE_STATUS_DISABLE,
101+
status: checked ? SubscribeStatusType.enable : SubscribeStatusType.disable,
102102
}),
103103
});
104104
});

src/pkg/backup/struct.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ export type SubscribeMeta = {
101101

102102
export type SubscribeOptionsFile = {
103103
settings: { enabled: boolean };
104-
scripts: { [key: string]: SubscribeScript };
104+
scripts: Record<string, SubscribeScript>;
105105
meta: SubscribeMeta;
106106
};
107107

0 commit comments

Comments
 (0)