Skip to content

Commit c5c3564

Browse files
CodFrmcyfung1031
andauthored
🐛 设备相关配置改用 chrome.storage.local,避免跨设备同步 (#1309)
* 🐛 设备相关配置改用 chrome.storage.local,避免跨设备同步导致的问题 - SystemConfig 支持双 storage(sync/local)+ 懒迁移 - 云同步前预检 OAuth token,无 token 不弹窗 - AuthVerify 加 mutex 防止并发弹窗 - 删除 changetime 死代码 close #1296 fix #1261 * 🔥 移除 token 预检和 OAuth 加锁(配置迁移后不再需要) * 📝 修正 check_update 注释描述 * 分离 const * 修正异步Storage操作处理 --------- Co-authored-by: cyfung1031 <44498510+cyfung1031@users.noreply.github.com>
1 parent 98aa494 commit c5c3564

3 files changed

Lines changed: 232 additions & 24 deletions

File tree

src/pkg/config/config.test.ts

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
import { describe, expect, it, beforeEach } from "vitest";
2+
import { SystemConfig } from "./config";
3+
import { MessageQueue } from "@Packages/message/message_queue";
4+
5+
describe("SystemConfig 双 storage 与懒迁移", () => {
6+
let mq: MessageQueue;
7+
let config: SystemConfig;
8+
9+
beforeEach(() => {
10+
// 清空 storage 数据
11+
chrome.storage.sync.clear();
12+
chrome.storage.local.clear();
13+
mq = new MessageQueue();
14+
config = new SystemConfig(mq);
15+
});
16+
17+
describe("local key 读写", () => {
18+
it("cloud_sync 应写入 local storage 而非 sync", async () => {
19+
const cloudSync = {
20+
enable: true,
21+
syncDelete: false,
22+
syncStatus: true,
23+
filesystem: "onedrive" as const,
24+
params: { token: "test" },
25+
};
26+
config.setCloudSync(cloudSync);
27+
28+
const result = await config.getCloudSync();
29+
expect(result).toEqual(cloudSync);
30+
31+
// 验证值在 local 中
32+
const localData = await chrome.storage.local.get("system_cloud_sync");
33+
expect(localData["system_cloud_sync"]).toEqual(cloudSync);
34+
35+
// 验证值不在 sync 中
36+
const syncData = await chrome.storage.sync.get("system_cloud_sync");
37+
expect(syncData["system_cloud_sync"]).toBeUndefined();
38+
});
39+
40+
it("language 应写入 local storage", async () => {
41+
config.setLanguage("zh-CN");
42+
43+
// 通过 storage 验证写入位置
44+
const localData = await chrome.storage.local.get("system_language");
45+
expect(localData["system_language"]).toBe("zh-CN");
46+
47+
const syncData = await chrome.storage.sync.get("system_language");
48+
expect(syncData["system_language"]).toBeUndefined();
49+
});
50+
51+
it("vscode_url 应写入 local storage", async () => {
52+
config.setVscodeUrl("ws://localhost:9999");
53+
54+
const localData = await chrome.storage.local.get("system_vscode_url");
55+
expect(localData["system_vscode_url"]).toBe("ws://localhost:9999");
56+
57+
const syncData = await chrome.storage.sync.get("system_vscode_url");
58+
expect(syncData["system_vscode_url"]).toBeUndefined();
59+
});
60+
61+
it("enable_script 应写入 local storage", async () => {
62+
config.setEnableScript(false);
63+
64+
const localData = await chrome.storage.local.get("system_enable_script");
65+
expect(localData["system_enable_script"]).toBe(false);
66+
67+
const syncData = await chrome.storage.sync.get("system_enable_script");
68+
expect(syncData["system_enable_script"]).toBeUndefined();
69+
});
70+
});
71+
72+
describe("sync key 读写", () => {
73+
it("check_script_update_cycle 应写入 sync storage", async () => {
74+
config.setCheckScriptUpdateCycle(3600);
75+
76+
const syncData = await chrome.storage.sync.get("system_check_script_update_cycle");
77+
expect(syncData["system_check_script_update_cycle"]).toBe(3600);
78+
79+
const localData = await chrome.storage.local.get("system_check_script_update_cycle");
80+
expect(localData["system_check_script_update_cycle"]).toBeUndefined();
81+
});
82+
83+
it("enable_eslint 应写入 sync storage", async () => {
84+
config.setEnableEslint(false);
85+
86+
const syncData = await chrome.storage.sync.get("system_enable_eslint");
87+
expect(syncData["system_enable_eslint"]).toBe(false);
88+
89+
const localData = await chrome.storage.local.get("system_enable_eslint");
90+
expect(localData["system_enable_eslint"]).toBeUndefined();
91+
});
92+
});
93+
94+
describe("懒迁移:sync → local", () => {
95+
it("local key 的旧数据应从 sync 迁移到 local", async () => {
96+
// 模拟旧版本数据在 sync 中
97+
const oldCloudSync = {
98+
enable: true,
99+
syncDelete: false,
100+
syncStatus: true,
101+
filesystem: "webdav" as const,
102+
params: { url: "https://example.com" },
103+
};
104+
await chrome.storage.sync.set({ system_cloud_sync: oldCloudSync });
105+
106+
// 读取时应自动迁移
107+
const result = await config.getCloudSync();
108+
expect(result).toEqual(oldCloudSync);
109+
110+
// 验证已迁移到 local
111+
const localData = await chrome.storage.local.get("system_cloud_sync");
112+
expect(localData["system_cloud_sync"]).toEqual(oldCloudSync);
113+
114+
// 验证已从 sync 中删除
115+
const syncData = await chrome.storage.sync.get("system_cloud_sync");
116+
expect(syncData["system_cloud_sync"]).toBeUndefined();
117+
});
118+
119+
it("local 有值时不应回退到 sync", async () => {
120+
const localValue = "zh-CN";
121+
const syncValue = "en-US";
122+
await chrome.storage.local.set({ system_language: localValue });
123+
await chrome.storage.sync.set({ system_language: syncValue });
124+
125+
const result = await config.getLanguage();
126+
expect(result).toBe(localValue);
127+
128+
// sync 中的值不应被删除(因为 local 有值,不触发迁移)
129+
const syncData = await chrome.storage.sync.get("system_language");
130+
expect(syncData["system_language"]).toBe(syncValue);
131+
});
132+
133+
it("sync 和 local 都没有值时返回默认值", async () => {
134+
const result = await config.getCloudSync();
135+
expect(result).toEqual({
136+
enable: false,
137+
syncDelete: false,
138+
syncStatus: true,
139+
filesystem: "webdav",
140+
params: {},
141+
});
142+
});
143+
144+
it("迁移后再次读取应走缓存", async () => {
145+
await chrome.storage.sync.set({ system_vscode_url: "ws://old:8642" });
146+
147+
// 第一次读取触发迁移
148+
const first = await config.getVscodeUrl();
149+
expect(first).toBe("ws://old:8642");
150+
151+
// 修改 local storage(模拟外部写入),验证缓存生效
152+
await chrome.storage.local.set({ system_vscode_url: "ws://new:9999" });
153+
154+
// 第二次读取应返回缓存值
155+
const second = await config.getVscodeUrl();
156+
expect(second).toBe("ws://old:8642");
157+
});
158+
});
159+
});

src/pkg/config/config.ts

Lines changed: 59 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { ExtVersion } from "@App/app/const";
88
import defaultTypeDefinition from "@App/template/scriptcat.d.tpl";
99
import { toCamelCase } from "../utils/utils";
1010
import EventEmitter from "eventemitter3";
11+
import { STORAGE_LOCAL_KEYS } from "./consts";
1112

1213
export const SystemConfigChange = "systemConfigChange";
1314

@@ -73,7 +74,19 @@ export type SystemConfigValueType<K extends SystemConfigKey> =
7374
export class SystemConfig {
7475
private readonly cache = new Map<string, any>();
7576

76-
private readonly storage = new ChromeStorage("system", true);
77+
// 跨设备同步的配置项,使用 chrome.storage.sync
78+
private readonly syncStorage = new ChromeStorage("system", true);
79+
// 设备相关的配置项,使用 chrome.storage.local(不跨设备同步)
80+
private readonly localStorage = new ChromeStorage("system", false);
81+
82+
private isLocalKey(key: string): boolean {
83+
return STORAGE_LOCAL_KEYS.has(key);
84+
}
85+
86+
// 获取 key 对应的主 storage
87+
private getStorage(key: string): ChromeStorage {
88+
return this.isLocalKey(key) ? this.localStorage : this.syncStorage;
89+
}
7790

7891
private EE: EventEmitter<string> = new EventEmitter<string>();
7992

@@ -108,21 +121,47 @@ export class SystemConfig {
108121
return this.addListener(key, callback);
109122
}
110123

124+
private resolveDefault<T>(defaultValue: WithAsyncValue<Exclude<T, undefined>>): T | Promise<T> {
125+
//@ts-ignore
126+
return (defaultValue?.asyncValue?.() || defaultValue) as T | Promise<T>;
127+
}
128+
129+
private async transferSyncToLocal<T>(
130+
key: SystemConfigKey,
131+
defaultValue: WithAsyncValue<Exclude<T, undefined>>
132+
): Promise<T> {
133+
const syncVal = await this.syncStorage.get(key);
134+
if (syncVal === undefined) {
135+
this.cache.set(key, undefined);
136+
return this.resolveDefault<T>(defaultValue);
137+
}
138+
// 迁移到 local storage 并从 sync 中删除
139+
await this.syncStorage.remove(key); // 先删除
140+
await this.localStorage.set(key, syncVal); // 删除成功后储回本地
141+
this.cache.set(key, syncVal);
142+
return syncVal as T;
143+
}
144+
111145
private _get<T extends string | number | boolean | object>(
112146
key: SystemConfigKey,
113147
defaultValue: WithAsyncValue<Exclude<T, undefined>>
114148
): Promise<T> {
115149
if (this.cache.has(key)) {
116-
let val = this.cache.get(key);
117-
//@ts-ignore
118-
val = (val === undefined ? defaultValue?.asyncValue?.() || defaultValue : val) as T | Promise<T>;
119-
return Promise.resolve(val);
150+
const val = this.cache.get(key);
151+
return Promise.resolve(val === undefined ? this.resolveDefault<T>(defaultValue) : (val as T));
120152
}
121-
return this.storage.get(key).then((val) => {
153+
const storage = this.getStorage(key);
154+
return storage.get(key).then((val) => {
155+
if (val !== undefined) {
156+
this.cache.set(key, val);
157+
return val as T;
158+
}
159+
// 对 local key,回退读取 sync storage(兼容旧版本数据迁移)
160+
if (this.isLocalKey(key)) {
161+
return this.transferSyncToLocal<T>(key, defaultValue);
162+
}
122163
this.cache.set(key, val);
123-
//@ts-ignore
124-
val = (val === undefined ? defaultValue?.asyncValue?.() || defaultValue : val) as T | Promise<T>;
125-
return val;
164+
return this.resolveDefault<T>(defaultValue);
126165
});
127166
}
128167

@@ -150,29 +189,25 @@ export class SystemConfig {
150189

151190
private _set<T extends SystemConfigKey>(key: T, value: SystemConfigValueType<T> | undefined) {
152191
const prev = this.cache.get(key);
192+
const storage = this.getStorage(key);
193+
let asyncOp;
153194
if (value === undefined) {
154195
this.cache.delete(key);
155-
this.storage.remove(key);
196+
asyncOp = storage.remove(key);
156197
} else {
157198
this.cache.set(key, value);
158-
this.storage.set(key, value);
199+
asyncOp = storage.set(key, value);
159200
}
160-
// 发送消息通知更新
161-
this.mq.publish<TKeyValue<T>>(SystemConfigChange, {
162-
key,
163-
value,
164-
prev,
201+
asyncOp.then(() => {
202+
// 发送消息通知更新
203+
this.mq.publish<TKeyValue<T>>(SystemConfigChange, {
204+
key,
205+
value,
206+
prev,
207+
});
165208
});
166209
}
167210

168-
public getChangetime() {
169-
return this._get<number>("changetime", 0);
170-
}
171-
172-
public setChangetime(n: number) {
173-
this._set("changetime", n);
174-
}
175-
176211
defaultCheckScriptUpdateCycle() {
177212
return 86400;
178213
}

src/pkg/config/consts.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// 设备相关的配置项,存储在 chrome.storage.local 而非 sync
2+
// 这些配置不应跨设备同步(如云同步认证、VSCode 连接、UI 布局等)
3+
export const STORAGE_LOCAL_KEYS: Set<string> = new Set([
4+
"cloud_sync", // 云同步配置(token 存在本地,不应跨设备同步)
5+
"backup", // 备份配置(含设备相关 filesystem params)
6+
"cat_file_storage", // CAT 文件存储配置
7+
"vscode_url", // VSCode 连接地址(设备相关)
8+
"vscode_reconnect", // VSCode 自动重连
9+
"language", // 语言偏好(可能因设备不同)
10+
"script_list_column_width", // UI 列宽(取决于屏幕尺寸)
11+
"check_update", // 扩展更新通知及已读状态(各设备已读状态独立)
12+
"enable_script", // 全局脚本开关(设备独立)
13+
"enable_script_incognito", // 隐身模式开关(浏览器级别)
14+
]);

0 commit comments

Comments
 (0)