Skip to content

Commit 0ca7e93

Browse files
committed
refactor(plugins): ensure plugin metadata is real-time and read-only
1 parent 1283871 commit 0ca7e93

5 files changed

Lines changed: 122 additions & 21 deletions

File tree

bridge/bridge.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ var Env = &EnvResult{
2828
FromTaskSch: false,
2929
WebviewPath: "",
3030
AppName: "",
31-
AppVersion: "v1.20.0-dev.2",
31+
AppVersion: "v1.20.0-dev.3",
3232
BasePath: "",
3333
OS: sysruntime.GOOS,
3434
ARCH: sysruntime.GOARCH,

frontend/.env

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
VITE_APP_TITLE = GUI.for.Clash
2-
VITE_APP_VERSION = v1.20.0-dev.2
2+
VITE_APP_VERSION = v1.20.0-dev.3
33
VITE_APP_PROJECT_URL = https://github.com/GUI-for-Cores/GUI.for.Clash
44
VITE_APP_VERSION_API = https://api.github.com/repos/GUI-for-Cores/GUI.for.Clash/releases/latest
55
VITE_APP_LOCALES_URL = https://github.com/GUI-for-Cores/GUI-for-Cores.github.io/tree/main/app-resources/locales/gfc

frontend/src/stores/plugins.ts

Lines changed: 86 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
deepClone,
1616
confirm,
1717
asyncPool,
18+
readonly,
1819
} from '@/utils'
1920

2021
import type { Plugin, Subscription, TrayContent, MenuItem } from '@/types/app'
@@ -102,28 +103,92 @@ export const usePluginsStore = defineStore('plugins', () => {
102103
}
103104
}
104105

105-
const getPluginMetadata = (plugin: Plugin) => {
106-
const configuration: Recordable = {}
107-
for (const { key, value } of plugin.configuration) {
108-
configuration[key] = value
106+
const getPluginMetadata = (id: string) => {
107+
const lastConfiguration: Recordable = { time: 0, data: undefined }
108+
const buildConfiguration = (plugin: Plugin) => {
109+
const now = performance.now()
110+
if (lastConfiguration.data && now - lastConfiguration.time < 1000) {
111+
return lastConfiguration.data
112+
}
113+
114+
const configuration: Recordable = {}
115+
for (const { key, value } of plugin.configuration) {
116+
configuration[key] = value
117+
}
118+
119+
const userSettings = appSettingsStore.app.pluginSettings[plugin.id]
120+
if (userSettings) {
121+
for (const key in userSettings) {
122+
configuration[key] = userSettings[key]
123+
}
124+
}
125+
126+
lastConfiguration.time = now
127+
lastConfiguration.data = configuration
128+
return configuration
109129
}
110-
Object.assign(configuration, appSettingsStore.app.pluginSettings[plugin.id] ?? {})
111-
const metadata = deepClone({ ...plugin, ...configuration })
112-
const proxy = new Proxy(metadata, {
113-
get(target, p, receiver) {
114-
return Reflect.get(target, p, receiver)
130+
131+
const lastPlugin: { time: number; data: Plugin | undefined } = { time: 0, data: undefined }
132+
const getPlugin = () => {
133+
const now = performance.now()
134+
if (lastPlugin.data && now - lastPlugin.time < 1000) {
135+
return lastPlugin.data
136+
}
137+
const cache = PluginsCache[id]
138+
if (!cache) throw new Error()
139+
140+
lastPlugin.time = now
141+
lastPlugin.data = cache.plugin
142+
return cache.plugin
143+
}
144+
145+
const proxy = new Proxy({} as Plugin & Recordable, {
146+
get(_, p) {
147+
const plugin = getPlugin()
148+
if (typeof p === 'string' && p.startsWith('__v_')) {
149+
return Reflect.get(plugin, p)
150+
}
151+
152+
let value
153+
if (Object.hasOwn(plugin, p)) {
154+
value = Reflect.get(plugin, p)
155+
} else {
156+
const configuration = buildConfiguration(plugin)
157+
value = Reflect.get(configuration, p)
158+
}
159+
160+
if (p === 'status') return value
161+
162+
return readonly(value)
115163
},
116-
set(target, p, newValue, receiver) {
164+
165+
set(_, p, newValue) {
166+
const plugin = getPlugin()
167+
117168
if (p === 'status') {
118169
plugin.status = newValue
119170
editPlugin(plugin.id, plugin)
120-
Reflect.set(target, p, newValue, receiver)
121171
return true
122172
}
173+
123174
console.warn(`[${plugin.name}] Property "${String(p)}" is read-only.`)
124175
return false
125176
},
177+
178+
ownKeys() {
179+
const plugin = getPlugin()
180+
const configuration = buildConfiguration(plugin)
181+
return [...Reflect.ownKeys(plugin), ...Reflect.ownKeys(configuration)]
182+
},
183+
184+
getOwnPropertyDescriptor() {
185+
return {
186+
enumerable: true,
187+
configurable: true,
188+
}
189+
},
126190
})
191+
127192
return proxy
128193
}
129194

@@ -209,6 +274,9 @@ export const usePluginsStore = defineStore('plugins', () => {
209274
const plugin = plugins.value.splice(idx, 1, newPlugin)[0]!
210275
try {
211276
await savePlugins()
277+
if (PluginsCache[plugin.id]) {
278+
PluginsCache[plugin.id]!.plugin = newPlugin
279+
}
212280
updatePluginTrigger(newPlugin)
213281
} catch (error) {
214282
plugins.value.splice(idx, 1, plugin)
@@ -352,7 +420,8 @@ export const usePluginsStore = defineStore('plugins', () => {
352420

353421
if (isPluginUnavailable(cache)) continue
354422

355-
const metadata = getPluginMetadata(cache.plugin)
423+
const metadata = getPluginMetadata(observer)
424+
356425
try {
357426
const fn = new window.AsyncFunction(
358427
'Plugin',
@@ -382,7 +451,7 @@ export const usePluginsStore = defineStore('plugins', () => {
382451

383452
if (isPluginUnavailable(cache)) continue
384453

385-
const metadata = getPluginMetadata(cache.plugin)
454+
const metadata = getPluginMetadata(observer)
386455
try {
387456
const fn = new window.AsyncFunction('Plugin', `${cache.code}; return await ${fnName}()`)
388457
const exitCode = await fn(metadata)
@@ -411,7 +480,7 @@ export const usePluginsStore = defineStore('plugins', () => {
411480

412481
if (isPluginUnavailable(cache)) continue
413482

414-
const metadata = getPluginMetadata(cache.plugin)
483+
const metadata = getPluginMetadata(observer)
415484
try {
416485
const fn = new window.AsyncFunction(
417486
'Plugin',
@@ -441,7 +510,7 @@ export const usePluginsStore = defineStore('plugins', () => {
441510

442511
if (isPluginUnavailable(cache)) continue
443512

444-
const metadata = getPluginMetadata(cache.plugin)
513+
const metadata = getPluginMetadata(observer)
445514
try {
446515
const fn = new window.AsyncFunction(
447516
'Plugin',
@@ -466,7 +535,7 @@ export const usePluginsStore = defineStore('plugins', () => {
466535
const cache = PluginsCache[plugin.id]
467536
if (!cache) throw `${plugin.name} is Missing source code`
468537
if (cache.plugin.disabled) throw `${plugin.name} is Disabled`
469-
const metadata = getPluginMetadata(plugin)
538+
const metadata = getPluginMetadata(id)
470539
args = deepClone(args)
471540
try {
472541
const fn = new window.AsyncFunction(
@@ -497,7 +566,7 @@ export const usePluginsStore = defineStore('plugins', () => {
497566

498567
if (isPluginUnavailable(cache)) continue
499568

500-
const metadata = getPluginMetadata(cache.plugin)
569+
const metadata = getPluginMetadata(observer)
501570
try {
502571
const fn = new window.AsyncFunction(
503572
'Plugin',

frontend/src/utils/others.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,38 @@ export const deepAssign = (...args: any[]) => {
272272
return target
273273
}
274274

275+
export const readonly = <T>(obj: T): T => {
276+
if (typeof obj !== 'object' || obj === null) return obj
277+
return new Proxy(obj, {
278+
get(target, key) {
279+
const result = Reflect.get(target, key)
280+
if (typeof result === 'object' && result !== null) {
281+
return readonly(result)
282+
}
283+
return result
284+
},
285+
set(target, key) {
286+
console.warn(`Set operation on key "${String(key)}" failed: target is readonly.`, target)
287+
return true
288+
},
289+
deleteProperty(target, key) {
290+
console.warn(`Delete operation on key "${String(key)}" failed: target is readonly.`, target)
291+
return true
292+
},
293+
defineProperty(target, key) {
294+
console.warn(
295+
`DefineProperty operation on key "${String(key)}" failed: target is readonly.`,
296+
target,
297+
)
298+
return false
299+
},
300+
setPrototypeOf(target) {
301+
console.warn(`SetPrototypeOf operation failed: target is readonly.`, target)
302+
return false
303+
},
304+
})
305+
}
306+
275307
export const base64Encode = (str: string) => {
276308
return btoa(
277309
encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, (match, p1) =>

frontend/src/views/PluginsView/components/PluginView.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ const handleTest = async (event: PluginTriggerEvent, arg1?: any, arg2?: any) =>
5050
testing.value = true
5151
try {
5252
const metadata = JSON.stringify({
53-
...pluginsStore.getPluginMetadata(plugin.value),
53+
...pluginsStore.getPluginMetadata(props.id),
5454
Mode: 'Dev',
5555
})
5656
if (event === PluginTriggerEvent.OnSubscribe) {
@@ -87,7 +87,7 @@ const initPluginCode = async (p: Plugin) => {
8787
const p = pluginsStore.getPluginById(props.id)
8888
if (p) {
8989
plugin.value = deepClone(p)
90-
metadata.value = pluginsStore.getPluginMetadata(plugin.value)
90+
metadata.value = pluginsStore.getPluginMetadata(props.id)
9191
initPluginCode(p)
9292
}
9393

0 commit comments

Comments
 (0)