@chatbi-v/core 2.1.0 → 2.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-QET56C3T.mjs +51 -0
- package/dist/chunk-QET56C3T.mjs.map +1 -0
- package/dist/config-manager-3TKURRUT.mjs +9 -0
- package/dist/config-manager-3TKURRUT.mjs.map +1 -0
- package/dist/index.d.mts +1748 -0
- package/dist/index.d.ts +1745 -27
- package/dist/index.js +2833 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2685 -7
- package/dist/index.mjs.map +1 -0
- package/package.json +3 -3
- package/dist/adapters/local-storage-adapter.d.ts +0 -61
- package/dist/adapters/scoped-storage-adapter.d.ts +0 -61
- package/dist/api/adapters/axios-adapter.d.ts +0 -10
- package/dist/api/engine.d.ts +0 -87
- package/dist/api/index.d.ts +0 -6
- package/dist/api/utils.d.ts +0 -14
- package/dist/api-context.d.ts +0 -8
- package/dist/application/service-registry.d.ts +0 -57
- package/dist/chunk-O74KYN5N.mjs +0 -1
- package/dist/components/PluginErrorBoundary.d.ts +0 -44
- package/dist/components/PluginSlot.d.ts +0 -35
- package/dist/components/SlotSkeletons.d.ts +0 -27
- package/dist/config-manager-LQITPSUA.mjs +0 -1
- package/dist/config-manager.d.ts +0 -34
- package/dist/domain/auto-loader.d.ts +0 -36
- package/dist/domain/models.d.ts +0 -42
- package/dist/domain/plugin-manager.d.ts +0 -215
- package/dist/domain/plugin-runtime.d.ts +0 -70
- package/dist/domain/plugin-sandbox.d.ts +0 -40
- package/dist/domain/storage-manager.d.ts +0 -74
- package/dist/event-bus.d.ts +0 -38
- package/dist/hooks/use-plugin-loader.d.ts +0 -35
- package/dist/hooks/use-storage-state.d.ts +0 -15
- package/dist/index.cjs +0 -12
- package/dist/plugin-context.d.ts +0 -8
- package/dist/ports/api-port.d.ts +0 -132
- package/dist/ports/event-bus-port.d.ts +0 -32
- package/dist/ports/plugin-port.d.ts +0 -308
- package/dist/ports/storage-port.d.ts +0 -49
- package/dist/sandbox/proxy-sandbox.d.ts +0 -45
- package/dist/sandbox/style-isolation.d.ts +0 -13
- package/dist/utils/date.d.ts +0 -32
- package/dist/utils/index.d.ts +0 -4
- package/dist/utils/logger.d.ts +0 -79
- package/dist/utils/url.d.ts +0 -16
package/dist/index.js
ADDED
|
@@ -0,0 +1,2833 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __esm = (fn, res) => function __init() {
|
|
9
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
10
|
+
};
|
|
11
|
+
var __export = (target, all) => {
|
|
12
|
+
for (var name in all)
|
|
13
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
14
|
+
};
|
|
15
|
+
var __copyProps = (to, from, except, desc) => {
|
|
16
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
17
|
+
for (let key of __getOwnPropNames(from))
|
|
18
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
19
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
20
|
+
}
|
|
21
|
+
return to;
|
|
22
|
+
};
|
|
23
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
24
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
25
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
26
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
27
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
28
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
29
|
+
mod
|
|
30
|
+
));
|
|
31
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
32
|
+
|
|
33
|
+
// src/config-manager.ts
|
|
34
|
+
var config_manager_exports = {};
|
|
35
|
+
__export(config_manager_exports, {
|
|
36
|
+
ConfigManager: () => ConfigManager,
|
|
37
|
+
configManager: () => configManager
|
|
38
|
+
});
|
|
39
|
+
var ConfigManager, configManager;
|
|
40
|
+
var init_config_manager = __esm({
|
|
41
|
+
"src/config-manager.ts"() {
|
|
42
|
+
"use strict";
|
|
43
|
+
ConfigManager = class {
|
|
44
|
+
/** 内部存储配置的 Map 对象 */
|
|
45
|
+
config = /* @__PURE__ */ new Map();
|
|
46
|
+
/**
|
|
47
|
+
* 设置特定的配置项
|
|
48
|
+
* @param key - 配置键名(如 'system' 或插件 ID)
|
|
49
|
+
* @param value - 配置内容对象
|
|
50
|
+
*/
|
|
51
|
+
set(key, value) {
|
|
52
|
+
this.config.set(key, value);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* 获取指定的配置项
|
|
56
|
+
* @param key - 配置键名
|
|
57
|
+
* @returns 配置内容,若不存在则返回 undefined
|
|
58
|
+
*/
|
|
59
|
+
get(key) {
|
|
60
|
+
return this.config.get(key);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* 批量合并配置对象
|
|
64
|
+
* @description 对顶层属性进行浅合并,若属性值为对象则进行一层深度的合并。
|
|
65
|
+
* @param config - 待合并的配置对象映射
|
|
66
|
+
*/
|
|
67
|
+
merge(config) {
|
|
68
|
+
Object.keys(config).forEach((key) => {
|
|
69
|
+
const existing = this.config.get(key);
|
|
70
|
+
const incoming = config[key];
|
|
71
|
+
if (existing && typeof existing === "object" && incoming && typeof incoming === "object") {
|
|
72
|
+
this.config.set(key, { ...existing, ...incoming });
|
|
73
|
+
} else {
|
|
74
|
+
this.config.set(key, incoming);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* 获取当前存储的所有配置快照
|
|
80
|
+
* @returns 包含所有配置的普通对象
|
|
81
|
+
*/
|
|
82
|
+
getAll() {
|
|
83
|
+
return Object.fromEntries(this.config);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
configManager = new ConfigManager();
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// src/index.ts
|
|
91
|
+
var index_exports = {};
|
|
92
|
+
__export(index_exports, {
|
|
93
|
+
ApiEngine: () => ApiEngine,
|
|
94
|
+
ApiProvider: () => ApiProvider,
|
|
95
|
+
AvatarSkeleton: () => AvatarSkeleton,
|
|
96
|
+
AxiosAdapter: () => AxiosAdapter,
|
|
97
|
+
BasePlugin: () => BasePlugin,
|
|
98
|
+
BlockSkeleton: () => BlockSkeleton,
|
|
99
|
+
ConfigManager: () => ConfigManager,
|
|
100
|
+
DefaultEventBus: () => DefaultEventBus,
|
|
101
|
+
LocalStorageAdapter: () => LocalStorageAdapter,
|
|
102
|
+
LogLevel: () => LogLevel,
|
|
103
|
+
Logger: () => Logger,
|
|
104
|
+
PLUGIN_TYPES: () => PLUGIN_TYPES,
|
|
105
|
+
PluginErrorBoundary: () => PluginErrorBoundary,
|
|
106
|
+
PluginManager: () => PluginManager,
|
|
107
|
+
PluginProvider: () => PluginProvider,
|
|
108
|
+
PluginRuntime: () => PluginRuntime,
|
|
109
|
+
PluginSandbox: () => PluginSandbox,
|
|
110
|
+
PluginSlot: () => PluginSlot,
|
|
111
|
+
ProxySandbox: () => ProxySandbox,
|
|
112
|
+
SUCCESS_CODE: () => SUCCESS_CODE,
|
|
113
|
+
ScopedStorageAdapter: () => ScopedStorageAdapter,
|
|
114
|
+
ServiceRegistry: () => ServiceRegistry,
|
|
115
|
+
SidebarIconSkeleton: () => SidebarIconSkeleton,
|
|
116
|
+
Slot: () => Slot,
|
|
117
|
+
SlotSkeletons: () => SlotSkeletons,
|
|
118
|
+
StatusBarItemSkeleton: () => StatusBarItemSkeleton,
|
|
119
|
+
StorageManager: () => StorageManager,
|
|
120
|
+
apiEngine: () => apiEngine,
|
|
121
|
+
cleanUrlParams: () => cleanUrlParams,
|
|
122
|
+
configManager: () => configManager,
|
|
123
|
+
createLogger: () => createLogger,
|
|
124
|
+
dateUtils: () => dateUtils,
|
|
125
|
+
definePlugin: () => definePlugin,
|
|
126
|
+
isMockMode: () => isMockMode,
|
|
127
|
+
logger: () => logger,
|
|
128
|
+
normalizeParams: () => normalizeParams,
|
|
129
|
+
pluginManager: () => pluginManager,
|
|
130
|
+
resolveApiModules: () => resolveApiModules,
|
|
131
|
+
resolvePluginRegistry: () => resolvePluginRegistry,
|
|
132
|
+
serviceRegistry: () => serviceRegistry,
|
|
133
|
+
useApi: () => useApi,
|
|
134
|
+
usePluginLoader: () => usePluginLoader,
|
|
135
|
+
usePluginManager: () => usePluginManager,
|
|
136
|
+
useStorageState: () => useStorageState,
|
|
137
|
+
version: () => version
|
|
138
|
+
});
|
|
139
|
+
module.exports = __toCommonJS(index_exports);
|
|
140
|
+
|
|
141
|
+
// src/ports/plugin-port.ts
|
|
142
|
+
var PLUGIN_TYPES = ["business", "functional", "view", "theme", "renderer", "system"];
|
|
143
|
+
var Slot = {
|
|
144
|
+
Sidebar: "sidebar",
|
|
145
|
+
SidebarPanel: "sidebar-panel",
|
|
146
|
+
Header: "header",
|
|
147
|
+
StatusBar: "status-bar",
|
|
148
|
+
Settings: "settings",
|
|
149
|
+
MessageRenderer: "message-renderer",
|
|
150
|
+
MessageContentRenderer: "message-content-renderer",
|
|
151
|
+
SidebarSystem: "sidebar-system",
|
|
152
|
+
SidebarBottom: "sidebar-bottom",
|
|
153
|
+
RootLayout: "root-layout",
|
|
154
|
+
Custom: "custom"
|
|
155
|
+
};
|
|
156
|
+
var BasePlugin = class {
|
|
157
|
+
/**
|
|
158
|
+
* 插件 ID
|
|
159
|
+
* @description 自动从 metadata.id 获取
|
|
160
|
+
*/
|
|
161
|
+
get id() {
|
|
162
|
+
return this.metadata.id;
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
function definePlugin(plugin) {
|
|
166
|
+
return {
|
|
167
|
+
...plugin,
|
|
168
|
+
get id() {
|
|
169
|
+
return this.metadata.id;
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// src/utils/logger.ts
|
|
175
|
+
var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
|
|
176
|
+
LogLevel2[LogLevel2["DEBUG"] = 0] = "DEBUG";
|
|
177
|
+
LogLevel2[LogLevel2["INFO"] = 1] = "INFO";
|
|
178
|
+
LogLevel2[LogLevel2["WARN"] = 2] = "WARN";
|
|
179
|
+
LogLevel2[LogLevel2["ERROR"] = 3] = "ERROR";
|
|
180
|
+
LogLevel2[LogLevel2["NONE"] = 4] = "NONE";
|
|
181
|
+
return LogLevel2;
|
|
182
|
+
})(LogLevel || {});
|
|
183
|
+
function getColorForString(str) {
|
|
184
|
+
let hash = 5381;
|
|
185
|
+
for (let i = 0; i < str.length; i++) {
|
|
186
|
+
hash = (hash << 5) + hash + str.charCodeAt(i);
|
|
187
|
+
}
|
|
188
|
+
const hue = Math.abs(hash % 360);
|
|
189
|
+
return `hsl(${hue}, 70%, 45%)`;
|
|
190
|
+
}
|
|
191
|
+
var ANSI = {
|
|
192
|
+
reset: "\x1B[0m",
|
|
193
|
+
bold: "\x1B[1m",
|
|
194
|
+
dim: "\x1B[2m",
|
|
195
|
+
colors: [
|
|
196
|
+
"\x1B[31m",
|
|
197
|
+
// Red
|
|
198
|
+
"\x1B[32m",
|
|
199
|
+
// Green
|
|
200
|
+
"\x1B[33m",
|
|
201
|
+
// Yellow
|
|
202
|
+
"\x1B[34m",
|
|
203
|
+
// Blue
|
|
204
|
+
"\x1B[35m",
|
|
205
|
+
// Magenta
|
|
206
|
+
"\x1B[36m"
|
|
207
|
+
// Cyan
|
|
208
|
+
]
|
|
209
|
+
};
|
|
210
|
+
function getAnsiColorForString(str) {
|
|
211
|
+
let hash = 0;
|
|
212
|
+
for (let i = 0; i < str.length; i++) {
|
|
213
|
+
hash = str.charCodeAt(i) + ((hash << 5) - hash);
|
|
214
|
+
}
|
|
215
|
+
const index = Math.abs(hash % ANSI.colors.length);
|
|
216
|
+
return ANSI.colors[index];
|
|
217
|
+
}
|
|
218
|
+
var Logger = class _Logger {
|
|
219
|
+
/** 全局静态日志等级,所有实例共享 */
|
|
220
|
+
static level = 1 /* INFO */;
|
|
221
|
+
/** 日志缓冲区 */
|
|
222
|
+
static buffer = [];
|
|
223
|
+
/** 缓冲输出防抖定时器 */
|
|
224
|
+
static flushTimer = null;
|
|
225
|
+
/** 缓冲时间窗口 (ms) */
|
|
226
|
+
static FLUSH_INTERVAL = 100;
|
|
227
|
+
/** 是否启用缓冲模式 */
|
|
228
|
+
static bufferEnabled = true;
|
|
229
|
+
/** 是否为浏览器环境 */
|
|
230
|
+
static isBrowser = typeof window !== "undefined" && typeof document !== "undefined";
|
|
231
|
+
/** 当前实例的业务模块前缀 */
|
|
232
|
+
prefix;
|
|
233
|
+
/** 实例特定的颜色 */
|
|
234
|
+
color;
|
|
235
|
+
/**
|
|
236
|
+
* 构造函数
|
|
237
|
+
* @param prefix - 业务模块前缀,默认为 'App'
|
|
238
|
+
*/
|
|
239
|
+
constructor(prefix = "App") {
|
|
240
|
+
this.prefix = prefix;
|
|
241
|
+
this.color = getColorForString(prefix);
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* 静态方法:设置全局日志输出等级
|
|
245
|
+
* @param level - 目标日志等级
|
|
246
|
+
*/
|
|
247
|
+
static setLevel(level) {
|
|
248
|
+
this.level = level;
|
|
249
|
+
console.log(`[Logger] Global log level set to: ${LogLevel[level]}`);
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* 静态方法:启用或禁用日志缓冲
|
|
253
|
+
* @param enabled - 是否启用
|
|
254
|
+
*/
|
|
255
|
+
static setBufferEnabled(enabled) {
|
|
256
|
+
this.bufferEnabled = enabled;
|
|
257
|
+
if (!enabled) {
|
|
258
|
+
this.flush();
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* 静态方法:获取当前全局日志等级
|
|
263
|
+
* @returns 当前生效的全局日志等级
|
|
264
|
+
*/
|
|
265
|
+
static getLevel() {
|
|
266
|
+
return this.level;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* 格式化输出前缀
|
|
270
|
+
*/
|
|
271
|
+
getFormattedPrefix(prefix, level) {
|
|
272
|
+
if (_Logger.isBrowser) {
|
|
273
|
+
const color = getColorForString(prefix);
|
|
274
|
+
const style = [
|
|
275
|
+
`background: ${color}`,
|
|
276
|
+
"color: white",
|
|
277
|
+
"border-radius: 3px",
|
|
278
|
+
"padding: 2px 6px",
|
|
279
|
+
"font-weight: bold",
|
|
280
|
+
"font-family: monospace"
|
|
281
|
+
].join(";");
|
|
282
|
+
return [`%c ${prefix} `, style];
|
|
283
|
+
} else {
|
|
284
|
+
const colorCode = getAnsiColorForString(prefix);
|
|
285
|
+
return [`${colorCode}[${prefix}]${ANSI.reset}`];
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* 生成分组标题参数
|
|
290
|
+
*/
|
|
291
|
+
static getGroupTitleArgs(prefix, count, countDetails = "") {
|
|
292
|
+
if (_Logger.isBrowser) {
|
|
293
|
+
const color = getColorForString(prefix);
|
|
294
|
+
const badgeStyle = `background: ${color}; color: white; border-radius: 3px; padding: 2px 6px; font-weight: bold;`;
|
|
295
|
+
const titleStyle = `color: ${color}; font-weight: bold;`;
|
|
296
|
+
const countStyle = "color: gray; font-size: 0.9em;";
|
|
297
|
+
const countStr = count > 0 ? `(${count > 1 ? count + " messages" : "1 message"}${countDetails ? ", " + countDetails : ""})` : "";
|
|
298
|
+
return [`%c ${prefix} %c ${countStr}`, badgeStyle, countStyle];
|
|
299
|
+
} else {
|
|
300
|
+
const colorCode = getAnsiColorForString(prefix);
|
|
301
|
+
const countStr = count > 0 ? `(${count > 1 ? count + " messages" : "1 message"}${countDetails ? ", " + countDetails : ""})` : "";
|
|
302
|
+
return [`${colorCode}[${prefix}]${ANSI.reset} ${ANSI.dim}${countStr}${ANSI.reset}`];
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* 内部方法:将日志推入缓冲区或直接输出
|
|
307
|
+
*/
|
|
308
|
+
log(level, ...args) {
|
|
309
|
+
if (_Logger.level > level) return;
|
|
310
|
+
if (level === 3 /* ERROR */) {
|
|
311
|
+
_Logger.flush();
|
|
312
|
+
const prefixArgs = this.getFormattedPrefix(this.prefix, level);
|
|
313
|
+
console.error(...prefixArgs, ...args);
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
if (!_Logger.bufferEnabled) {
|
|
317
|
+
const method = level === 2 /* WARN */ ? "warn" : level === 0 /* DEBUG */ ? "debug" : "info";
|
|
318
|
+
const groupArgs = _Logger.getGroupTitleArgs(this.prefix, 1);
|
|
319
|
+
console.groupCollapsed(...groupArgs);
|
|
320
|
+
console[method](...args);
|
|
321
|
+
console.groupEnd();
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
_Logger.buffer.push({
|
|
325
|
+
level,
|
|
326
|
+
prefix: this.prefix,
|
|
327
|
+
message: args,
|
|
328
|
+
timestamp: Date.now()
|
|
329
|
+
});
|
|
330
|
+
if (!_Logger.flushTimer) {
|
|
331
|
+
_Logger.flushTimer = setTimeout(() => {
|
|
332
|
+
_Logger.flush();
|
|
333
|
+
}, _Logger.FLUSH_INTERVAL);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* 静态方法:刷新缓冲区,分组输出日志
|
|
338
|
+
*/
|
|
339
|
+
static flush() {
|
|
340
|
+
if (this.flushTimer) {
|
|
341
|
+
clearTimeout(this.flushTimer);
|
|
342
|
+
this.flushTimer = null;
|
|
343
|
+
}
|
|
344
|
+
if (this.buffer.length === 0) return;
|
|
345
|
+
const groups = /* @__PURE__ */ new Map();
|
|
346
|
+
const prefixOrder = [];
|
|
347
|
+
this.buffer.forEach((entry) => {
|
|
348
|
+
if (!groups.has(entry.prefix)) {
|
|
349
|
+
groups.set(entry.prefix, []);
|
|
350
|
+
prefixOrder.push(entry.prefix);
|
|
351
|
+
}
|
|
352
|
+
groups.get(entry.prefix).push(entry);
|
|
353
|
+
});
|
|
354
|
+
prefixOrder.forEach((prefix) => {
|
|
355
|
+
const entries = groups.get(prefix);
|
|
356
|
+
const counts = entries.reduce((acc, curr) => {
|
|
357
|
+
const key = curr.level === 2 /* WARN */ ? "warn" : "info";
|
|
358
|
+
acc[key] = (acc[key] || 0) + 1;
|
|
359
|
+
return acc;
|
|
360
|
+
}, {});
|
|
361
|
+
const countDetails = Object.entries(counts).map(([k, v]) => `${v} ${k}`).join(", ");
|
|
362
|
+
const groupTitleArgs = _Logger.getGroupTitleArgs(prefix, entries.length, entries.length > 1 ? countDetails : "");
|
|
363
|
+
console.groupCollapsed(...groupTitleArgs);
|
|
364
|
+
entries.forEach((entry) => {
|
|
365
|
+
const method = entry.level === 2 /* WARN */ ? "warn" : entry.level === 0 /* DEBUG */ ? "debug" : "info";
|
|
366
|
+
console[method](...entry.message);
|
|
367
|
+
});
|
|
368
|
+
console.groupEnd();
|
|
369
|
+
});
|
|
370
|
+
this.buffer = [];
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* 打印 DEBUG 级别日志
|
|
374
|
+
*/
|
|
375
|
+
debug(...args) {
|
|
376
|
+
this.log(0 /* DEBUG */, ...args);
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* 打印 INFO 级别日志
|
|
380
|
+
*/
|
|
381
|
+
info(...args) {
|
|
382
|
+
this.log(1 /* INFO */, ...args);
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* 打印 WARN 级别日志
|
|
386
|
+
*/
|
|
387
|
+
warn(...args) {
|
|
388
|
+
this.log(2 /* WARN */, ...args);
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* 打印 ERROR 级别日志
|
|
392
|
+
*/
|
|
393
|
+
error(...args) {
|
|
394
|
+
this.log(3 /* ERROR */, ...args);
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* 开始一个日志控制台分组
|
|
398
|
+
*/
|
|
399
|
+
group(label, collapsed = false) {
|
|
400
|
+
if (_Logger.level <= 1 /* INFO */) {
|
|
401
|
+
_Logger.flush();
|
|
402
|
+
const prefixArgs = this.getFormattedPrefix(this.prefix, 1 /* INFO */);
|
|
403
|
+
if (_Logger.isBrowser) {
|
|
404
|
+
if (collapsed) {
|
|
405
|
+
console.groupCollapsed(...prefixArgs, label);
|
|
406
|
+
} else {
|
|
407
|
+
console.group(...prefixArgs, label);
|
|
408
|
+
}
|
|
409
|
+
} else {
|
|
410
|
+
const title = `${prefixArgs[0]} ${label}`;
|
|
411
|
+
if (collapsed) {
|
|
412
|
+
console.groupCollapsed(title);
|
|
413
|
+
} else {
|
|
414
|
+
console.group(title);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* 结束当前日志控制台分组
|
|
421
|
+
*/
|
|
422
|
+
groupEnd() {
|
|
423
|
+
if (_Logger.level <= 1 /* INFO */) {
|
|
424
|
+
_Logger.flush();
|
|
425
|
+
console.groupEnd();
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
};
|
|
429
|
+
var logger = new Logger();
|
|
430
|
+
var createLogger = (prefix) => new Logger(prefix);
|
|
431
|
+
|
|
432
|
+
// src/application/service-registry.ts
|
|
433
|
+
var logger2 = createLogger("ServiceRegistry");
|
|
434
|
+
var ServiceRegistry = class {
|
|
435
|
+
/** 存储已注册服务的 Map 对象 */
|
|
436
|
+
services = /* @__PURE__ */ new Map();
|
|
437
|
+
/** 存储正在等待特定服务的监听器集合 */
|
|
438
|
+
listeners = /* @__PURE__ */ new Map();
|
|
439
|
+
/**
|
|
440
|
+
* 注册一个服务实现
|
|
441
|
+
* @param name - 唯一的服务名称
|
|
442
|
+
* @param service - 服务实例或对象
|
|
443
|
+
*/
|
|
444
|
+
register(name, service) {
|
|
445
|
+
if (this.services.has(name)) {
|
|
446
|
+
logger2.warn(`\u670D\u52A1 "${name}" \u5DF2\u5B58\u5728\uFF0C\u5C06\u88AB\u8986\u76D6`);
|
|
447
|
+
}
|
|
448
|
+
this.services.set(name, service);
|
|
449
|
+
logger2.info(`\u670D\u52A1\u5DF2\u6CE8\u518C: ${name}`);
|
|
450
|
+
if (this.listeners.has(name)) {
|
|
451
|
+
const set = this.listeners.get(name);
|
|
452
|
+
set.forEach((callback) => callback(service));
|
|
453
|
+
this.listeners.delete(name);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* 同步获取服务实例
|
|
458
|
+
* @template T - 服务类型的泛型
|
|
459
|
+
* @param name - 服务名称
|
|
460
|
+
* @returns 服务实例,若不存在则返回 undefined
|
|
461
|
+
*/
|
|
462
|
+
get(name) {
|
|
463
|
+
return this.services.get(name);
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* 异步等待并获取服务
|
|
467
|
+
* @description 如果服务尚未注册,将返回一个 Promise,直到该服务被注册时 resolve。
|
|
468
|
+
* 支持设置超时时间,防止因插件加载失败导致的永久挂起。
|
|
469
|
+
*
|
|
470
|
+
* @template T - 服务类型的泛型
|
|
471
|
+
* @param name - 待等待的服务名称
|
|
472
|
+
* @param timeout - 超时时间 (毫秒),默认 10000ms (10秒)。若为 0 则永不超时。
|
|
473
|
+
* @returns 包含服务实例的 Promise
|
|
474
|
+
* @throws {Error} 若在规定时间内服务未注册,则抛出超时异常。
|
|
475
|
+
*/
|
|
476
|
+
async waitFor(name, timeout = 1e4) {
|
|
477
|
+
const service = this.services.get(name);
|
|
478
|
+
if (service) return service;
|
|
479
|
+
return new Promise((resolve, reject) => {
|
|
480
|
+
let timer = null;
|
|
481
|
+
const callback = (s) => {
|
|
482
|
+
if (timer) clearTimeout(timer);
|
|
483
|
+
resolve(s);
|
|
484
|
+
};
|
|
485
|
+
if (!this.listeners.has(name)) {
|
|
486
|
+
this.listeners.set(name, /* @__PURE__ */ new Set());
|
|
487
|
+
}
|
|
488
|
+
this.listeners.get(name).add(callback);
|
|
489
|
+
if (timeout > 0) {
|
|
490
|
+
timer = setTimeout(() => {
|
|
491
|
+
const set = this.listeners.get(name);
|
|
492
|
+
if (set) {
|
|
493
|
+
set.delete(callback);
|
|
494
|
+
if (set.size === 0) this.listeners.delete(name);
|
|
495
|
+
}
|
|
496
|
+
reject(new Error(`\u7B49\u5F85\u670D\u52A1 "${name}" \u8D85\u65F6 (${timeout}ms)`));
|
|
497
|
+
}, timeout);
|
|
498
|
+
}
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* 检查服务是否已注册
|
|
503
|
+
* @param name - 服务名称
|
|
504
|
+
*/
|
|
505
|
+
has(name) {
|
|
506
|
+
return this.services.has(name);
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* 注销特定的服务
|
|
510
|
+
* @param name - 服务名称
|
|
511
|
+
*/
|
|
512
|
+
unregister(name) {
|
|
513
|
+
this.services.delete(name);
|
|
514
|
+
logger2.info(`\u670D\u52A1\u5DF2\u6CE8\u9500: ${name}`);
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* 清除所有已注册的服务和监听器
|
|
518
|
+
* @description 通常仅在系统重置或大型热更新时使用。
|
|
519
|
+
*/
|
|
520
|
+
clear() {
|
|
521
|
+
this.services.clear();
|
|
522
|
+
this.listeners.clear();
|
|
523
|
+
}
|
|
524
|
+
};
|
|
525
|
+
var serviceRegistry = new ServiceRegistry();
|
|
526
|
+
|
|
527
|
+
// src/api-context.tsx
|
|
528
|
+
var import_react = require("react");
|
|
529
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
530
|
+
var ApiContext = (0, import_react.createContext)(null);
|
|
531
|
+
var ApiProvider = ({ api, children }) => {
|
|
532
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ApiContext.Provider, { value: api, children });
|
|
533
|
+
};
|
|
534
|
+
var useApi = () => {
|
|
535
|
+
const context = (0, import_react.useContext)(ApiContext);
|
|
536
|
+
if (!context) {
|
|
537
|
+
throw new Error("useApi must be used within an ApiProvider");
|
|
538
|
+
}
|
|
539
|
+
return context;
|
|
540
|
+
};
|
|
541
|
+
|
|
542
|
+
// src/index.ts
|
|
543
|
+
init_config_manager();
|
|
544
|
+
|
|
545
|
+
// src/components/PluginErrorBoundary.tsx
|
|
546
|
+
var import_react3 = require("react");
|
|
547
|
+
|
|
548
|
+
// src/domain/plugin-manager.ts
|
|
549
|
+
var import_react2 = require("react");
|
|
550
|
+
|
|
551
|
+
// src/adapters/local-storage-adapter.ts
|
|
552
|
+
var LocalStorageAdapter = class {
|
|
553
|
+
/** 命名空间前缀,所有存入的键名都会自动添加此前缀 */
|
|
554
|
+
prefix;
|
|
555
|
+
/**
|
|
556
|
+
* 初始化适配器
|
|
557
|
+
* @param prefix - 可选的命名空间前缀(如 'chatbi'),默认为空字符串
|
|
558
|
+
*/
|
|
559
|
+
constructor(prefix = "") {
|
|
560
|
+
this.prefix = prefix;
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* 内部方法:计算实际存储的键名
|
|
564
|
+
* @param key - 业务层传入的原始键名
|
|
565
|
+
* @returns 拼接前缀后的物理键名
|
|
566
|
+
*/
|
|
567
|
+
getKey(key) {
|
|
568
|
+
return this.prefix ? `${this.prefix}:${key}` : key;
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* 内部方法:从物理键名还原业务键名
|
|
572
|
+
* @param namespacedKey - 物理存储中的完整键名
|
|
573
|
+
* @returns 还原后的原始键名,若前缀不匹配则返回 null
|
|
574
|
+
*/
|
|
575
|
+
getOriginalKey(namespacedKey) {
|
|
576
|
+
if (!this.prefix) return namespacedKey;
|
|
577
|
+
if (namespacedKey.startsWith(this.prefix + ":")) {
|
|
578
|
+
return namespacedKey.slice(this.prefix.length + 1);
|
|
579
|
+
}
|
|
580
|
+
return null;
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* 读取存储项
|
|
584
|
+
* @param key - 原始键名
|
|
585
|
+
* @returns 存储的字符串内容,不存在则返回 null
|
|
586
|
+
*/
|
|
587
|
+
getItem(key) {
|
|
588
|
+
return localStorage.getItem(this.getKey(key));
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* 写入存储项
|
|
592
|
+
* @param key - 原始键名
|
|
593
|
+
* @param value - 待存储的字符串值
|
|
594
|
+
*/
|
|
595
|
+
setItem(key, value) {
|
|
596
|
+
localStorage.setItem(this.getKey(key), value);
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* 移除特定的存储项
|
|
600
|
+
* @param key - 原始键名
|
|
601
|
+
*/
|
|
602
|
+
removeItem(key) {
|
|
603
|
+
localStorage.removeItem(this.getKey(key));
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* 清空存储空间
|
|
607
|
+
* @description
|
|
608
|
+
* - 若定义了前缀:仅删除以该前缀开头的键名,不影响其他数据。
|
|
609
|
+
* - 若未定义前缀:调用原生 `localStorage.clear()` 清空所有数据。
|
|
610
|
+
*/
|
|
611
|
+
clear() {
|
|
612
|
+
if (!this.prefix) {
|
|
613
|
+
localStorage.clear();
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
const keysToRemove = [];
|
|
617
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
618
|
+
const key = localStorage.key(i);
|
|
619
|
+
if (key && key.startsWith(this.prefix + ":")) {
|
|
620
|
+
keysToRemove.push(key);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
keysToRemove.forEach((key) => localStorage.removeItem(key));
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* 返回当前命名空间下的存储项总数
|
|
627
|
+
*/
|
|
628
|
+
get length() {
|
|
629
|
+
if (!this.prefix) return localStorage.length;
|
|
630
|
+
let count = 0;
|
|
631
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
632
|
+
const key = localStorage.key(i);
|
|
633
|
+
if (key && key.startsWith(this.prefix + ":")) {
|
|
634
|
+
count++;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
return count;
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* 根据索引获取对应的原始键名
|
|
641
|
+
* @param index - 索引位置
|
|
642
|
+
* @returns 对应的业务键名,不存在则返回 null
|
|
643
|
+
*/
|
|
644
|
+
key(index) {
|
|
645
|
+
if (!this.prefix) return localStorage.key(index);
|
|
646
|
+
let count = 0;
|
|
647
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
648
|
+
const key = localStorage.key(i);
|
|
649
|
+
if (key && key.startsWith(this.prefix + ":")) {
|
|
650
|
+
if (count === index) {
|
|
651
|
+
return this.getOriginalKey(key);
|
|
652
|
+
}
|
|
653
|
+
count++;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
return null;
|
|
657
|
+
}
|
|
658
|
+
};
|
|
659
|
+
|
|
660
|
+
// src/domain/plugin-manager.ts
|
|
661
|
+
init_config_manager();
|
|
662
|
+
|
|
663
|
+
// src/event-bus.ts
|
|
664
|
+
var logger3 = createLogger("EventBus");
|
|
665
|
+
var DefaultEventBus = class {
|
|
666
|
+
listeners = /* @__PURE__ */ new Map();
|
|
667
|
+
on(event, callback) {
|
|
668
|
+
if (!this.listeners.has(event)) {
|
|
669
|
+
this.listeners.set(event, []);
|
|
670
|
+
}
|
|
671
|
+
this.listeners.get(event)?.push(callback);
|
|
672
|
+
return () => this.off(event, callback);
|
|
673
|
+
}
|
|
674
|
+
off(event, callback) {
|
|
675
|
+
const callbacks = this.listeners.get(event);
|
|
676
|
+
if (callbacks) {
|
|
677
|
+
this.listeners.set(event, callbacks.filter((cb) => cb !== callback));
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
emit(event, ...args) {
|
|
681
|
+
const callbacks = this.listeners.get(event);
|
|
682
|
+
if (callbacks) {
|
|
683
|
+
callbacks.forEach((cb) => {
|
|
684
|
+
try {
|
|
685
|
+
cb(...args);
|
|
686
|
+
} catch (error) {
|
|
687
|
+
logger3.error(`\u4E8B\u4EF6\u76D1\u542C\u5668\u5904\u7406\u9519\u8BEF (${event}):`, error);
|
|
688
|
+
}
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
once(event, callback) {
|
|
693
|
+
const wrapper = (...args) => {
|
|
694
|
+
callback(...args);
|
|
695
|
+
this.off(event, wrapper);
|
|
696
|
+
};
|
|
697
|
+
this.on(event, wrapper);
|
|
698
|
+
}
|
|
699
|
+
};
|
|
700
|
+
|
|
701
|
+
// src/sandbox/proxy-sandbox.ts
|
|
702
|
+
var logger4 = createLogger("ProxySandbox");
|
|
703
|
+
var ProxySandbox = class {
|
|
704
|
+
/** 沙箱名称 */
|
|
705
|
+
name;
|
|
706
|
+
/** 代理后的 Window 对象 */
|
|
707
|
+
proxy;
|
|
708
|
+
/** 沙箱是否激活 */
|
|
709
|
+
running = false;
|
|
710
|
+
/** 记录新增/修改的全局变量 */
|
|
711
|
+
updatedValueSet = /* @__PURE__ */ new Set();
|
|
712
|
+
/** 绑定函数的缓存池,避免重复 bind 带来的性能开销 */
|
|
713
|
+
boundFunctionCache = /* @__PURE__ */ new Map();
|
|
714
|
+
/** 副作用记录池 */
|
|
715
|
+
effectPool = {
|
|
716
|
+
timeouts: /* @__PURE__ */ new Set(),
|
|
717
|
+
intervals: /* @__PURE__ */ new Set(),
|
|
718
|
+
rafs: /* @__PURE__ */ new Set(),
|
|
719
|
+
// requestAnimationFrame
|
|
720
|
+
listeners: /* @__PURE__ */ new Map()
|
|
721
|
+
};
|
|
722
|
+
/** 真实的 Window 对象 */
|
|
723
|
+
globalContext;
|
|
724
|
+
/** 白名单全局变量(允许透传访问真实 Window) */
|
|
725
|
+
static globalWhitelist = [
|
|
726
|
+
"System",
|
|
727
|
+
"console",
|
|
728
|
+
// 'setTimeout', // 移除白名单,改为劫持
|
|
729
|
+
// 'setInterval',
|
|
730
|
+
// 'clearTimeout',
|
|
731
|
+
// 'clearInterval',
|
|
732
|
+
"requestAnimationFrame",
|
|
733
|
+
"cancelAnimationFrame",
|
|
734
|
+
// 'addEventListener', // 移除白名单,改为劫持
|
|
735
|
+
// 'removeEventListener',
|
|
736
|
+
"location",
|
|
737
|
+
"history",
|
|
738
|
+
"navigator",
|
|
739
|
+
"document"
|
|
740
|
+
];
|
|
741
|
+
constructor(name, globalContext = window) {
|
|
742
|
+
this.name = name;
|
|
743
|
+
this.globalContext = globalContext;
|
|
744
|
+
const { fakeWindow, propertiesWithGetter } = this.createFakeWindow(globalContext);
|
|
745
|
+
this.patchGlobalEffects(fakeWindow);
|
|
746
|
+
const proxy = new Proxy(fakeWindow, {
|
|
747
|
+
set: (target, p, value) => {
|
|
748
|
+
if (this.running) {
|
|
749
|
+
this.updatedValueSet.add(p);
|
|
750
|
+
target[p] = value;
|
|
751
|
+
return true;
|
|
752
|
+
}
|
|
753
|
+
logger4.warn(`${name} \u672A\u8FD0\u884C\uFF0C\u65E0\u6CD5\u8BBE\u7F6E\u5C5E\u6027 '${String(p)}'`);
|
|
754
|
+
return false;
|
|
755
|
+
},
|
|
756
|
+
get: (target, p) => {
|
|
757
|
+
if (p === Symbol.unscopables) return void 0;
|
|
758
|
+
if (p === "window" || p === "self" || p === "globalThis") {
|
|
759
|
+
return this.proxy;
|
|
760
|
+
}
|
|
761
|
+
if (p === "top" || p === "parent") {
|
|
762
|
+
return this.globalContext[p];
|
|
763
|
+
}
|
|
764
|
+
const value = target[p];
|
|
765
|
+
if (value !== void 0 || this.updatedValueSet.has(p)) {
|
|
766
|
+
return value;
|
|
767
|
+
}
|
|
768
|
+
const rawValue = this.globalContext[p];
|
|
769
|
+
if (typeof rawValue === "function" && !this.isConstructor(rawValue)) {
|
|
770
|
+
if (this.isNativeFunction(rawValue)) {
|
|
771
|
+
return rawValue.bind(this.globalContext);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
return rawValue;
|
|
775
|
+
},
|
|
776
|
+
has: (target, p) => {
|
|
777
|
+
return p in target || p in this.globalContext;
|
|
778
|
+
},
|
|
779
|
+
defineProperty: (target, p, attributes) => {
|
|
780
|
+
if (this.running) {
|
|
781
|
+
this.updatedValueSet.add(p);
|
|
782
|
+
return Reflect.defineProperty(target, p, attributes);
|
|
783
|
+
}
|
|
784
|
+
return false;
|
|
785
|
+
}
|
|
786
|
+
});
|
|
787
|
+
this.proxy = proxy;
|
|
788
|
+
}
|
|
789
|
+
/**
|
|
790
|
+
* 激活沙箱
|
|
791
|
+
*/
|
|
792
|
+
active() {
|
|
793
|
+
if (!this.running) {
|
|
794
|
+
this.running = true;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
/**
|
|
798
|
+
* 销毁沙箱
|
|
799
|
+
*/
|
|
800
|
+
inactive() {
|
|
801
|
+
this.running = false;
|
|
802
|
+
this.effectPool.timeouts.forEach((id) => this.globalContext.clearTimeout(id));
|
|
803
|
+
this.effectPool.timeouts.clear();
|
|
804
|
+
this.effectPool.intervals.forEach((id) => this.globalContext.clearInterval(id));
|
|
805
|
+
this.effectPool.intervals.clear();
|
|
806
|
+
this.effectPool.rafs.forEach((id) => this.globalContext.cancelAnimationFrame(id));
|
|
807
|
+
this.effectPool.rafs.clear();
|
|
808
|
+
this.effectPool.listeners.forEach((listeners, type) => {
|
|
809
|
+
listeners.forEach(({ listener, options }) => {
|
|
810
|
+
this.globalContext.removeEventListener(type, listener, options);
|
|
811
|
+
});
|
|
812
|
+
});
|
|
813
|
+
this.effectPool.listeners.clear();
|
|
814
|
+
logger4.debug(`${this.name} \u5DF2\u505C\u7528\uFF0C\u526F\u4F5C\u7528\u5DF2\u6E05\u9664\u3002`);
|
|
815
|
+
}
|
|
816
|
+
/**
|
|
817
|
+
* 在沙箱中执行代码
|
|
818
|
+
* @param code JS 代码字符串
|
|
819
|
+
* @returns 执行结果
|
|
820
|
+
*/
|
|
821
|
+
eval(code) {
|
|
822
|
+
const sourceURL = `//# sourceURL=${this.name}.js`;
|
|
823
|
+
const evalCode = `
|
|
824
|
+
;(function(window, self, globalThis){
|
|
825
|
+
with(window) {
|
|
826
|
+
${code}
|
|
827
|
+
${sourceURL}
|
|
828
|
+
}
|
|
829
|
+
})
|
|
830
|
+
`;
|
|
831
|
+
const func = (0, eval)(evalCode);
|
|
832
|
+
return func.call(this.proxy, this.proxy, this.proxy, this.proxy);
|
|
833
|
+
}
|
|
834
|
+
/**
|
|
835
|
+
* 创建伪造的 Window 对象
|
|
836
|
+
*/
|
|
837
|
+
createFakeWindow(globalContext) {
|
|
838
|
+
const propertiesWithGetter = /* @__PURE__ */ new Map();
|
|
839
|
+
const fakeWindow = {};
|
|
840
|
+
Object.getOwnPropertyNames(globalContext).forEach((p) => {
|
|
841
|
+
const descriptor = Object.getOwnPropertyDescriptor(globalContext, p);
|
|
842
|
+
if (descriptor && !descriptor.configurable) {
|
|
843
|
+
}
|
|
844
|
+
});
|
|
845
|
+
return { fakeWindow, propertiesWithGetter };
|
|
846
|
+
}
|
|
847
|
+
isConstructor(fn) {
|
|
848
|
+
const prototype = fn.prototype;
|
|
849
|
+
return !!(prototype && prototype.constructor === fn && Object.getOwnPropertyNames(prototype).length > 0);
|
|
850
|
+
}
|
|
851
|
+
isNativeFunction(fn) {
|
|
852
|
+
return fn.toString().indexOf("[native code]") > -1;
|
|
853
|
+
}
|
|
854
|
+
/**
|
|
855
|
+
* 劫持全局副作用 API
|
|
856
|
+
*/
|
|
857
|
+
patchGlobalEffects(fakeWindow) {
|
|
858
|
+
fakeWindow.setTimeout = (handler, timeout, ...args) => {
|
|
859
|
+
const id = this.globalContext.setTimeout(handler, timeout, ...args);
|
|
860
|
+
this.effectPool.timeouts.add(id);
|
|
861
|
+
return id;
|
|
862
|
+
};
|
|
863
|
+
fakeWindow.clearTimeout = (id) => {
|
|
864
|
+
if (id) {
|
|
865
|
+
this.effectPool.timeouts.delete(id);
|
|
866
|
+
this.globalContext.clearTimeout(id);
|
|
867
|
+
}
|
|
868
|
+
};
|
|
869
|
+
fakeWindow.setInterval = (handler, timeout, ...args) => {
|
|
870
|
+
const id = this.globalContext.setInterval(handler, timeout, ...args);
|
|
871
|
+
this.effectPool.intervals.add(id);
|
|
872
|
+
return id;
|
|
873
|
+
};
|
|
874
|
+
fakeWindow.clearInterval = (id) => {
|
|
875
|
+
if (id) {
|
|
876
|
+
this.effectPool.intervals.delete(id);
|
|
877
|
+
this.globalContext.clearInterval(id);
|
|
878
|
+
}
|
|
879
|
+
};
|
|
880
|
+
fakeWindow.requestAnimationFrame = (callback) => {
|
|
881
|
+
const id = this.globalContext.requestAnimationFrame(callback);
|
|
882
|
+
this.effectPool.rafs.add(id);
|
|
883
|
+
return id;
|
|
884
|
+
};
|
|
885
|
+
fakeWindow.cancelAnimationFrame = (id) => {
|
|
886
|
+
if (id) {
|
|
887
|
+
this.effectPool.rafs.delete(id);
|
|
888
|
+
this.globalContext.cancelAnimationFrame(id);
|
|
889
|
+
}
|
|
890
|
+
};
|
|
891
|
+
fakeWindow.addEventListener = (type, listener, options) => {
|
|
892
|
+
const listeners = this.effectPool.listeners.get(type) || [];
|
|
893
|
+
listeners.push({ listener, options });
|
|
894
|
+
this.effectPool.listeners.set(type, listeners);
|
|
895
|
+
return this.globalContext.addEventListener(type, listener, options);
|
|
896
|
+
};
|
|
897
|
+
fakeWindow.removeEventListener = (type, listener, options) => {
|
|
898
|
+
const listeners = this.effectPool.listeners.get(type);
|
|
899
|
+
if (listeners) {
|
|
900
|
+
const index = listeners.findIndex((item) => item.listener === listener && item.options === options);
|
|
901
|
+
if (index !== -1) listeners.splice(index, 1);
|
|
902
|
+
}
|
|
903
|
+
return this.globalContext.removeEventListener(type, listener, options);
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
};
|
|
907
|
+
|
|
908
|
+
// src/domain/plugin-sandbox.ts
|
|
909
|
+
var PluginSandbox = class {
|
|
910
|
+
/** 关联的插件唯一标识 */
|
|
911
|
+
pluginId;
|
|
912
|
+
/** 系统全局存储管理器 */
|
|
913
|
+
storageManager;
|
|
914
|
+
/**
|
|
915
|
+
* 构造插件沙箱
|
|
916
|
+
* @param pluginId - 插件 ID
|
|
917
|
+
* @param storageManager - 系统存储管理器实例
|
|
918
|
+
*/
|
|
919
|
+
constructor(pluginId, storageManager) {
|
|
920
|
+
this.pluginId = pluginId;
|
|
921
|
+
this.storageManager = storageManager;
|
|
922
|
+
}
|
|
923
|
+
/**
|
|
924
|
+
* 获取隔离的存储接口
|
|
925
|
+
* @description 返回一个受限的 StoragePort,所有操作都会自动带上插件 ID 前缀。
|
|
926
|
+
*/
|
|
927
|
+
get storage() {
|
|
928
|
+
return this.storageManager.getContextStorage(this.pluginId);
|
|
929
|
+
}
|
|
930
|
+
/**
|
|
931
|
+
* 获取隔离的日志接口
|
|
932
|
+
* @description 返回一个带插件 ID 前缀的 Logger 实例。
|
|
933
|
+
*/
|
|
934
|
+
get logger() {
|
|
935
|
+
return createLogger(`Plugin:${this.pluginId}`);
|
|
936
|
+
}
|
|
937
|
+
};
|
|
938
|
+
|
|
939
|
+
// src/domain/plugin-runtime.ts
|
|
940
|
+
var logger5 = createLogger("PluginRuntime");
|
|
941
|
+
var PluginRuntime = class {
|
|
942
|
+
/** 插件定义对象 */
|
|
943
|
+
plugin;
|
|
944
|
+
/** 传递给插件的上下文对象 */
|
|
945
|
+
context;
|
|
946
|
+
/** 存储沙箱隔离设施 */
|
|
947
|
+
storageSandbox;
|
|
948
|
+
/** Window/全局对象沙箱隔离设施 */
|
|
949
|
+
windowSandbox;
|
|
950
|
+
/** 是否已完成加载阶段 */
|
|
951
|
+
isLoaded = false;
|
|
952
|
+
/** 是否已完成挂载阶段 */
|
|
953
|
+
isMounted = false;
|
|
954
|
+
/** 运行时捕获的错误信息 */
|
|
955
|
+
error = null;
|
|
956
|
+
/**
|
|
957
|
+
* 构造函数
|
|
958
|
+
* @param plugin - 插件定义对象
|
|
959
|
+
* @param sharedContext - 来自内核的共享上下文资源 (如 API 引擎、事件总线)
|
|
960
|
+
* @param storageManager - 全局存储管理器
|
|
961
|
+
*/
|
|
962
|
+
constructor(plugin, sharedContext, storageManager) {
|
|
963
|
+
this.plugin = plugin;
|
|
964
|
+
this.storageSandbox = new PluginSandbox(plugin.id, storageManager);
|
|
965
|
+
this.windowSandbox = new ProxySandbox(plugin.id);
|
|
966
|
+
this.context = {
|
|
967
|
+
pluginId: plugin.id,
|
|
968
|
+
api: sharedContext.api,
|
|
969
|
+
events: sharedContext.events,
|
|
970
|
+
storage: this.storageSandbox.storage,
|
|
971
|
+
logger: this.storageSandbox.logger,
|
|
972
|
+
window: this.windowSandbox.proxy,
|
|
973
|
+
getService: (name) => serviceRegistry.get(name),
|
|
974
|
+
registerService: (name, service) => serviceRegistry.register(`${plugin.id}.${name}`, service)
|
|
975
|
+
};
|
|
976
|
+
}
|
|
977
|
+
/**
|
|
978
|
+
* 执行插件的加载逻辑 (onLoad)
|
|
979
|
+
* @description 此阶段会自动注册插件声明的 API 配置,并调用插件的 onLoad 钩子。
|
|
980
|
+
* 用于执行非 UI 的初始化逻辑,如注册服务、拦截器等。
|
|
981
|
+
*/
|
|
982
|
+
async load() {
|
|
983
|
+
if (this.isLoaded) return;
|
|
984
|
+
logger5.debug(`\u6B63\u5728\u52A0\u8F7D\u63D2\u4EF6: ${this.plugin.id}`);
|
|
985
|
+
try {
|
|
986
|
+
if (this.plugin.metadata.api && this.context.api && typeof this.context.api.register === "function") {
|
|
987
|
+
this.context.api.register(this.plugin.metadata.api);
|
|
988
|
+
logger5.debug(`\u5DF2\u4E3A\u63D2\u4EF6 ${this.plugin.id} \u81EA\u52A8\u6CE8\u518C API \u914D\u7F6E`);
|
|
989
|
+
}
|
|
990
|
+
if (this.plugin.metadata.configuration && this.plugin.metadata.configuration.length > 0) {
|
|
991
|
+
const configMap = new Map(this.plugin.metadata.configuration.map((c) => [c.key, c]));
|
|
992
|
+
const getterCache = /* @__PURE__ */ new Map();
|
|
993
|
+
const configService = new Proxy({}, {
|
|
994
|
+
get: (target, prop) => {
|
|
995
|
+
if (typeof prop !== "string") {
|
|
996
|
+
return Reflect.get(target, prop);
|
|
997
|
+
}
|
|
998
|
+
if (prop.startsWith("get")) {
|
|
999
|
+
if (getterCache.has(prop)) {
|
|
1000
|
+
return getterCache.get(prop);
|
|
1001
|
+
}
|
|
1002
|
+
const configKey = prop.slice(3).charAt(0).toLowerCase() + prop.slice(3).slice(1);
|
|
1003
|
+
const configItem = configMap.get(configKey);
|
|
1004
|
+
if (configItem) {
|
|
1005
|
+
if (configItem.private || configItem.internal) {
|
|
1006
|
+
logger5.warn(`\u5C1D\u8BD5\u8BBF\u95EE\u79C1\u6709\u914D\u7F6E: ${configKey}`);
|
|
1007
|
+
const noop = () => void 0;
|
|
1008
|
+
getterCache.set(prop, noop);
|
|
1009
|
+
return noop;
|
|
1010
|
+
}
|
|
1011
|
+
const getter = () => this.context.storage.get(configKey);
|
|
1012
|
+
getterCache.set(prop, getter);
|
|
1013
|
+
return getter;
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
return Reflect.get(target, prop);
|
|
1017
|
+
}
|
|
1018
|
+
});
|
|
1019
|
+
this.context.registerService("config", configService);
|
|
1020
|
+
logger5.debug(`\u5DF2\u4E3A\u63D2\u4EF6 ${this.plugin.id} \u81EA\u52A8\u6CE8\u518C\u914D\u7F6E\u670D\u52A1 (Proxy Mode)`);
|
|
1021
|
+
}
|
|
1022
|
+
if (this.plugin.onLoad) {
|
|
1023
|
+
await this.plugin.onLoad(this.context);
|
|
1024
|
+
}
|
|
1025
|
+
this.isLoaded = true;
|
|
1026
|
+
this.error = null;
|
|
1027
|
+
logger5.info(`\u63D2\u4EF6 ${this.plugin.id} \u5DF2\u52A0\u8F7D\u3002`);
|
|
1028
|
+
} catch (error) {
|
|
1029
|
+
this.error = error instanceof Error ? error : new Error(String(error));
|
|
1030
|
+
logger5.error(`\u63D2\u4EF6 ${this.plugin.id} \u52A0\u8F7D\u5931\u8D25:`, error);
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
/**
|
|
1034
|
+
* 执行插件的挂载逻辑 (onMount)
|
|
1035
|
+
* @description 此阶段会激活 Window 沙箱并调用插件的 onMount 钩子。
|
|
1036
|
+
* 此时插件的 UI 组件(如有)即将或已经进入 DOM。
|
|
1037
|
+
*/
|
|
1038
|
+
async mount() {
|
|
1039
|
+
if (!this.isLoaded) await this.load();
|
|
1040
|
+
if (this.isMounted) return;
|
|
1041
|
+
logger5.debug(`\u6B63\u5728\u6302\u8F7D\u63D2\u4EF6: ${this.plugin.id}`);
|
|
1042
|
+
try {
|
|
1043
|
+
this.windowSandbox.active();
|
|
1044
|
+
if (this.plugin.onMount) {
|
|
1045
|
+
this.plugin.onMount(this.context);
|
|
1046
|
+
}
|
|
1047
|
+
this.isMounted = true;
|
|
1048
|
+
this.error = null;
|
|
1049
|
+
logger5.info(`\u63D2\u4EF6 ${this.plugin.id} \u5DF2\u6302\u8F7D\u3002`);
|
|
1050
|
+
} catch (error) {
|
|
1051
|
+
this.error = error instanceof Error ? error : new Error(String(error));
|
|
1052
|
+
logger5.error(`\u63D2\u4EF6 ${this.plugin.id} \u6302\u8F7D\u5931\u8D25:`, error);
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
/**
|
|
1056
|
+
* 执行插件的卸载逻辑 (onUnmount)
|
|
1057
|
+
* @description 调用插件的 onUnmount 钩子并停用沙箱。
|
|
1058
|
+
*/
|
|
1059
|
+
async unmount() {
|
|
1060
|
+
if (!this.isMounted) return;
|
|
1061
|
+
logger5.debug(`\u6B63\u5728\u5378\u8F7D\u63D2\u4EF6: ${this.plugin.id}`);
|
|
1062
|
+
try {
|
|
1063
|
+
if (this.plugin.onUnmount) {
|
|
1064
|
+
this.plugin.onUnmount(this.context);
|
|
1065
|
+
}
|
|
1066
|
+
} catch (error) {
|
|
1067
|
+
logger5.error(`\u63D2\u4EF6 ${this.plugin.id} \u5378\u8F7D\u65F6\u51FA\u9519:`, error);
|
|
1068
|
+
} finally {
|
|
1069
|
+
this.windowSandbox.inactive();
|
|
1070
|
+
this.isMounted = false;
|
|
1071
|
+
this.error = null;
|
|
1072
|
+
logger5.info(`\u63D2\u4EF6 ${this.plugin.id} \u5DF2\u5378\u8F7D\u3002`);
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
/**
|
|
1076
|
+
* 彻底销毁插件实例
|
|
1077
|
+
* @description 卸载插件并重置加载状态。
|
|
1078
|
+
*/
|
|
1079
|
+
async destroy() {
|
|
1080
|
+
await this.unmount();
|
|
1081
|
+
this.isLoaded = false;
|
|
1082
|
+
this.error = null;
|
|
1083
|
+
}
|
|
1084
|
+
/**
|
|
1085
|
+
* 设置运行时错误 (内部使用)
|
|
1086
|
+
* @internal
|
|
1087
|
+
*/
|
|
1088
|
+
_setError(error) {
|
|
1089
|
+
this.error = error;
|
|
1090
|
+
}
|
|
1091
|
+
/**
|
|
1092
|
+
* 获取插件运行时错误
|
|
1093
|
+
*/
|
|
1094
|
+
getError() {
|
|
1095
|
+
return this.error;
|
|
1096
|
+
}
|
|
1097
|
+
/**
|
|
1098
|
+
* 获取插件当前运行状态
|
|
1099
|
+
* @returns 'error' | 'mounted' | 'loaded' | 'initial'
|
|
1100
|
+
*/
|
|
1101
|
+
get status() {
|
|
1102
|
+
if (this.error) return "error";
|
|
1103
|
+
if (this.isMounted) return "mounted";
|
|
1104
|
+
if (this.isLoaded) return "loaded";
|
|
1105
|
+
return "initial";
|
|
1106
|
+
}
|
|
1107
|
+
};
|
|
1108
|
+
|
|
1109
|
+
// src/adapters/scoped-storage-adapter.ts
|
|
1110
|
+
var ScopedStorageAdapter = class {
|
|
1111
|
+
/**
|
|
1112
|
+
* 初始化作用域适配器
|
|
1113
|
+
* @param underlyingStorage - 被装饰的底层存储适配器实例
|
|
1114
|
+
* @param prefix - 用于隔离的作用域前缀字符串
|
|
1115
|
+
*/
|
|
1116
|
+
constructor(underlyingStorage, prefix) {
|
|
1117
|
+
this.underlyingStorage = underlyingStorage;
|
|
1118
|
+
this.prefix = prefix;
|
|
1119
|
+
}
|
|
1120
|
+
/**
|
|
1121
|
+
* 内部方法:计算实际存储的键名
|
|
1122
|
+
* @param key - 业务层传入的原始键名
|
|
1123
|
+
* @returns 拼接前缀后的物理键名
|
|
1124
|
+
*/
|
|
1125
|
+
getKey(key) {
|
|
1126
|
+
return this.prefix ? `${this.prefix}:${key}` : key;
|
|
1127
|
+
}
|
|
1128
|
+
/**
|
|
1129
|
+
* 内部方法:从物理键名还原业务键名
|
|
1130
|
+
* @param namespacedKey - 物理存储中的完整键名
|
|
1131
|
+
* @returns 还原后的原始键名,若前缀不匹配则返回 null
|
|
1132
|
+
*/
|
|
1133
|
+
getOriginalKey(namespacedKey) {
|
|
1134
|
+
if (!this.prefix) return namespacedKey;
|
|
1135
|
+
if (namespacedKey.startsWith(this.prefix + ":")) {
|
|
1136
|
+
return namespacedKey.slice(this.prefix.length + 1);
|
|
1137
|
+
}
|
|
1138
|
+
return null;
|
|
1139
|
+
}
|
|
1140
|
+
/**
|
|
1141
|
+
* 读取当前作用域下的存储项
|
|
1142
|
+
* @param key - 原始键名
|
|
1143
|
+
* @returns 字符串内容,不存在则返回 null
|
|
1144
|
+
*/
|
|
1145
|
+
getItem(key) {
|
|
1146
|
+
return this.underlyingStorage.getItem(this.getKey(key));
|
|
1147
|
+
}
|
|
1148
|
+
/**
|
|
1149
|
+
* 写入当前作用域下的存储项
|
|
1150
|
+
* @param key - 原始键名
|
|
1151
|
+
* @param value - 字符串内容
|
|
1152
|
+
*/
|
|
1153
|
+
setItem(key, value) {
|
|
1154
|
+
this.underlyingStorage.setItem(this.getKey(key), value);
|
|
1155
|
+
}
|
|
1156
|
+
/**
|
|
1157
|
+
* 移除当前作用域下的特定存储项
|
|
1158
|
+
* @param key - 原始键名
|
|
1159
|
+
*/
|
|
1160
|
+
removeItem(key) {
|
|
1161
|
+
this.underlyingStorage.removeItem(this.getKey(key));
|
|
1162
|
+
}
|
|
1163
|
+
/**
|
|
1164
|
+
* 清空当前作用域下的所有存储项
|
|
1165
|
+
* @description 仅删除匹配当前前缀的键值对,不会影响底层存储中的其他数据。
|
|
1166
|
+
*/
|
|
1167
|
+
clear() {
|
|
1168
|
+
if (!this.prefix) {
|
|
1169
|
+
this.underlyingStorage.clear();
|
|
1170
|
+
return;
|
|
1171
|
+
}
|
|
1172
|
+
const keysToRemove = [];
|
|
1173
|
+
for (let i = 0; i < this.underlyingStorage.length; i++) {
|
|
1174
|
+
const key = this.underlyingStorage.key(i);
|
|
1175
|
+
if (key && key.startsWith(this.prefix + ":")) {
|
|
1176
|
+
keysToRemove.push(key);
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
keysToRemove.forEach((k) => this.underlyingStorage.removeItem(k));
|
|
1180
|
+
}
|
|
1181
|
+
/**
|
|
1182
|
+
* 返回当前作用域下的存储项总数
|
|
1183
|
+
*/
|
|
1184
|
+
get length() {
|
|
1185
|
+
let count = 0;
|
|
1186
|
+
for (let i = 0; i < this.underlyingStorage.length; i++) {
|
|
1187
|
+
const key = this.underlyingStorage.key(i);
|
|
1188
|
+
if (key && key.startsWith(this.prefix + ":")) {
|
|
1189
|
+
count++;
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
return count;
|
|
1193
|
+
}
|
|
1194
|
+
/**
|
|
1195
|
+
* 根据索引获取当前作用域下的原始键名
|
|
1196
|
+
* @param index - 索引位置
|
|
1197
|
+
* @returns 对应的业务键名,不存在则返回 null
|
|
1198
|
+
*/
|
|
1199
|
+
key(index) {
|
|
1200
|
+
let count = 0;
|
|
1201
|
+
for (let i = 0; i < this.underlyingStorage.length; i++) {
|
|
1202
|
+
const key = this.underlyingStorage.key(i);
|
|
1203
|
+
if (key && key.startsWith(this.prefix + ":")) {
|
|
1204
|
+
if (count === index) {
|
|
1205
|
+
return this.getOriginalKey(key);
|
|
1206
|
+
}
|
|
1207
|
+
count++;
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
return null;
|
|
1211
|
+
}
|
|
1212
|
+
};
|
|
1213
|
+
|
|
1214
|
+
// src/domain/storage-manager.ts
|
|
1215
|
+
init_config_manager();
|
|
1216
|
+
var StorageManager = class {
|
|
1217
|
+
/** 底层物理存储驱动 */
|
|
1218
|
+
baseStorage;
|
|
1219
|
+
/** 插件 ID 与存储描述定义的映射关系 */
|
|
1220
|
+
schemas = /* @__PURE__ */ new Map();
|
|
1221
|
+
/** 内存缓存,减少 JSON 序列化和物理 IO 开销 */
|
|
1222
|
+
memoryCache = /* @__PURE__ */ new Map();
|
|
1223
|
+
/**
|
|
1224
|
+
* 初始化存储管理器
|
|
1225
|
+
* @param baseStorage - 符合 StoragePort 接口的物理存储驱动(如 LocalStorageAdapter)
|
|
1226
|
+
*/
|
|
1227
|
+
constructor(baseStorage) {
|
|
1228
|
+
this.baseStorage = baseStorage;
|
|
1229
|
+
}
|
|
1230
|
+
/**
|
|
1231
|
+
* 注册插件的存储 Schema
|
|
1232
|
+
* @param pluginId - 插件 ID
|
|
1233
|
+
* @param schema - 存储项定义列表
|
|
1234
|
+
*/
|
|
1235
|
+
registerSchema(pluginId, schema) {
|
|
1236
|
+
this.schemas.set(pluginId, schema);
|
|
1237
|
+
}
|
|
1238
|
+
/**
|
|
1239
|
+
* 内部校验方法:检查键名是否在 Schema 中声明
|
|
1240
|
+
* @param pluginId - 插件 ID
|
|
1241
|
+
* @param key - 待校验的键名
|
|
1242
|
+
* @param scope - 目标作用域
|
|
1243
|
+
*/
|
|
1244
|
+
validateKey(pluginId, key, scope = "plugin") {
|
|
1245
|
+
const schemaList = this.schemas.get(pluginId);
|
|
1246
|
+
if (!schemaList) return;
|
|
1247
|
+
const item = schemaList.find((s) => s.key === key);
|
|
1248
|
+
if (!item) {
|
|
1249
|
+
console.warn(`[Storage] Key "${key}" not defined in plugin "${pluginId}" schema.`);
|
|
1250
|
+
return;
|
|
1251
|
+
}
|
|
1252
|
+
const definedScope = item.scope || "plugin";
|
|
1253
|
+
if (definedScope !== scope) {
|
|
1254
|
+
console.warn(`[Storage] Key "${key}" defined in scope "${definedScope}" but accessed via "${scope}".`);
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
/**
|
|
1258
|
+
* 获取插件私有存储适配器
|
|
1259
|
+
* @param pluginId - 插件 ID
|
|
1260
|
+
* @returns 自动添加 `plugin:{id}:` 前缀的存储接口
|
|
1261
|
+
*/
|
|
1262
|
+
getPluginStorage(pluginId) {
|
|
1263
|
+
return new ScopedStorageAdapter(this.baseStorage, `plugin:${pluginId}`);
|
|
1264
|
+
}
|
|
1265
|
+
/**
|
|
1266
|
+
* 获取全局共享存储适配器
|
|
1267
|
+
* @returns 自动添加 `shared:` 前缀的存储接口
|
|
1268
|
+
*/
|
|
1269
|
+
getSharedStorage() {
|
|
1270
|
+
return new ScopedStorageAdapter(this.baseStorage, "shared");
|
|
1271
|
+
}
|
|
1272
|
+
/**
|
|
1273
|
+
* 获取系统内部存储适配器
|
|
1274
|
+
* @returns 自动添加 `system:` 前缀的存储接口
|
|
1275
|
+
*/
|
|
1276
|
+
getSystemStorage() {
|
|
1277
|
+
return new ScopedStorageAdapter(this.baseStorage, "system");
|
|
1278
|
+
}
|
|
1279
|
+
/**
|
|
1280
|
+
* 创建插件沙箱使用的复合存储对象
|
|
1281
|
+
* @description 返回的对象封装了对私有存储和共享存储的操作。
|
|
1282
|
+
* 特性:
|
|
1283
|
+
* - 内存级 LRU 缓存:极大提升高频读写性能。
|
|
1284
|
+
* - 自动序列化/反序列化 JSON。
|
|
1285
|
+
* - 自动校验 Schema。
|
|
1286
|
+
* - 取值回退逻辑:持久化存储 -> ConfigManager -> Schema 默认值。
|
|
1287
|
+
*
|
|
1288
|
+
* @param pluginId - 插件 ID
|
|
1289
|
+
* @returns 包含 get/set/remove 及 shared 子对象的复合接口
|
|
1290
|
+
*/
|
|
1291
|
+
getContextStorage(pluginId) {
|
|
1292
|
+
const pluginStorage = this.getPluginStorage(pluginId);
|
|
1293
|
+
const sharedStorage = this.getSharedStorage();
|
|
1294
|
+
const createHelpers = (adapter, scope) => {
|
|
1295
|
+
const prefix = scope === "plugin" ? `plugin:${pluginId}:` : "shared:";
|
|
1296
|
+
return {
|
|
1297
|
+
get: (key) => {
|
|
1298
|
+
this.validateKey(pluginId, key, scope);
|
|
1299
|
+
const cacheKey = `${prefix}${key}`;
|
|
1300
|
+
try {
|
|
1301
|
+
if (this.memoryCache.has(cacheKey)) {
|
|
1302
|
+
return this.memoryCache.get(cacheKey);
|
|
1303
|
+
}
|
|
1304
|
+
const val = adapter.getItem(key);
|
|
1305
|
+
if (val !== null) {
|
|
1306
|
+
let parsed;
|
|
1307
|
+
try {
|
|
1308
|
+
parsed = JSON.parse(val);
|
|
1309
|
+
} catch {
|
|
1310
|
+
parsed = val;
|
|
1311
|
+
}
|
|
1312
|
+
this.memoryCache.set(cacheKey, parsed);
|
|
1313
|
+
return parsed;
|
|
1314
|
+
}
|
|
1315
|
+
if (scope === "plugin") {
|
|
1316
|
+
const externalConfig = configManager.get(pluginId);
|
|
1317
|
+
if (externalConfig && externalConfig[key] !== void 0) {
|
|
1318
|
+
return externalConfig[key];
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
const schema = this.schemas.get(pluginId)?.find((s) => s.key === key);
|
|
1322
|
+
if (schema && schema.default !== void 0) {
|
|
1323
|
+
return schema.default;
|
|
1324
|
+
}
|
|
1325
|
+
return null;
|
|
1326
|
+
} catch (e) {
|
|
1327
|
+
console.warn(`[Storage] Failed to read key "${key}"`, e);
|
|
1328
|
+
return null;
|
|
1329
|
+
}
|
|
1330
|
+
},
|
|
1331
|
+
set: (key, value) => {
|
|
1332
|
+
this.validateKey(pluginId, key, scope);
|
|
1333
|
+
const cacheKey = `${prefix}${key}`;
|
|
1334
|
+
try {
|
|
1335
|
+
this.memoryCache.set(cacheKey, value);
|
|
1336
|
+
adapter.setItem(key, JSON.stringify(value));
|
|
1337
|
+
} catch (e) {
|
|
1338
|
+
console.warn(`[Storage] Failed to stringify key "${key}"`, e);
|
|
1339
|
+
}
|
|
1340
|
+
},
|
|
1341
|
+
remove: (key) => {
|
|
1342
|
+
this.validateKey(pluginId, key, scope);
|
|
1343
|
+
const cacheKey = `${prefix}${key}`;
|
|
1344
|
+
this.memoryCache.delete(cacheKey);
|
|
1345
|
+
adapter.removeItem(key);
|
|
1346
|
+
}
|
|
1347
|
+
};
|
|
1348
|
+
};
|
|
1349
|
+
return {
|
|
1350
|
+
...createHelpers(pluginStorage, "plugin"),
|
|
1351
|
+
shared: createHelpers(sharedStorage, "shared")
|
|
1352
|
+
};
|
|
1353
|
+
}
|
|
1354
|
+
};
|
|
1355
|
+
|
|
1356
|
+
// src/domain/plugin-manager.ts
|
|
1357
|
+
var logger6 = createLogger("PluginManager");
|
|
1358
|
+
var PluginManager = class {
|
|
1359
|
+
/** 全局事件总线,用于插件间及插件与内核间的异步通信 */
|
|
1360
|
+
eventBus = new DefaultEventBus();
|
|
1361
|
+
/** 存储管理服务,负责插件私有存储和系统级状态的持久化 */
|
|
1362
|
+
storageManager;
|
|
1363
|
+
/** 插件 ID 到运行时实例的映射表 */
|
|
1364
|
+
runtimes = /* @__PURE__ */ new Map();
|
|
1365
|
+
/** 插件 ID 到原始插件定义对象的映射表 */
|
|
1366
|
+
plugins = /* @__PURE__ */ new Map();
|
|
1367
|
+
/** 收集到的所有插件路由配置 */
|
|
1368
|
+
routes = [];
|
|
1369
|
+
/** 收集到的所有插件扩展点配置,按插槽位置分组 */
|
|
1370
|
+
extensions = /* @__PURE__ */ new Map();
|
|
1371
|
+
/** 插件的启用状态和排序信息的内存缓存 */
|
|
1372
|
+
pluginStates = {};
|
|
1373
|
+
/** 状态变更监听器集合,支持按插槽过滤 */
|
|
1374
|
+
listeners = /* @__PURE__ */ new Set();
|
|
1375
|
+
/** 按插槽位置存储的监听器,用于精确通知 */
|
|
1376
|
+
slotListeners = /* @__PURE__ */ new Map();
|
|
1377
|
+
/** 扩展点缓存,避免重复计算 */
|
|
1378
|
+
memoizedExtensions = /* @__PURE__ */ new Map();
|
|
1379
|
+
/** 路由缓存 */
|
|
1380
|
+
memoizedRoutes = null;
|
|
1381
|
+
/** 传递给插件的共享上下文缓存 */
|
|
1382
|
+
sharedContext = null;
|
|
1383
|
+
/** 收集到的插件工具函数集合 */
|
|
1384
|
+
utils = {};
|
|
1385
|
+
/**
|
|
1386
|
+
* 构造函数
|
|
1387
|
+
* @param storage - 底层存储适配器
|
|
1388
|
+
*/
|
|
1389
|
+
constructor(storage) {
|
|
1390
|
+
this.storageManager = new StorageManager(storage);
|
|
1391
|
+
this.loadStates();
|
|
1392
|
+
this.subscribe(() => {
|
|
1393
|
+
this.saveStates();
|
|
1394
|
+
});
|
|
1395
|
+
}
|
|
1396
|
+
/**
|
|
1397
|
+
* 从持久化存储中恢复插件状态 (启用/禁用、排序等)
|
|
1398
|
+
*/
|
|
1399
|
+
loadStates() {
|
|
1400
|
+
try {
|
|
1401
|
+
const systemStorage = this.storageManager.getSystemStorage();
|
|
1402
|
+
const savedStates = systemStorage.getItem("plugin_states");
|
|
1403
|
+
if (savedStates) {
|
|
1404
|
+
this.pluginStates = JSON.parse(savedStates);
|
|
1405
|
+
logger6.debug("\u4ECE\u7CFB\u7EDF\u5B58\u50A8\u4E2D\u52A0\u8F7D\u72B6\u6001:", this.pluginStates);
|
|
1406
|
+
}
|
|
1407
|
+
} catch (e) {
|
|
1408
|
+
logger6.error("\u52A0\u8F7D\u63D2\u4EF6\u72B6\u6001\u5931\u8D25:", e);
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
/**
|
|
1412
|
+
* 将当前的插件状态持久化到存储中
|
|
1413
|
+
*/
|
|
1414
|
+
saveStates() {
|
|
1415
|
+
try {
|
|
1416
|
+
this.storageManager.getSystemStorage().setItem("plugin_states", JSON.stringify(this.pluginStates));
|
|
1417
|
+
logger6.debug("\u5DF2\u4FDD\u5B58\u63D2\u4EF6\u72B6\u6001\u5230\u5B58\u50A8");
|
|
1418
|
+
} catch (e) {
|
|
1419
|
+
logger6.error("\u4FDD\u5B58\u63D2\u4EF6\u72B6\u6001\u5931\u8D25:", e);
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
/**
|
|
1423
|
+
* 订阅插件状态的变更通知
|
|
1424
|
+
* @param listener - 变更时的回调函数
|
|
1425
|
+
* @param slot - (可选) 指定监听的插槽位置,若提供则仅在该插槽受影响时通知
|
|
1426
|
+
* @returns 取消订阅的函数
|
|
1427
|
+
*/
|
|
1428
|
+
subscribe(listener, slot) {
|
|
1429
|
+
if (slot) {
|
|
1430
|
+
const slotStr = String(slot);
|
|
1431
|
+
if (!this.slotListeners.has(slotStr)) {
|
|
1432
|
+
this.slotListeners.set(slotStr, /* @__PURE__ */ new Set());
|
|
1433
|
+
}
|
|
1434
|
+
this.slotListeners.get(slotStr).add(listener);
|
|
1435
|
+
return () => {
|
|
1436
|
+
this.slotListeners.get(slotStr)?.delete(listener);
|
|
1437
|
+
};
|
|
1438
|
+
} else {
|
|
1439
|
+
this.listeners.add(listener);
|
|
1440
|
+
return () => {
|
|
1441
|
+
this.listeners.delete(listener);
|
|
1442
|
+
};
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
/**
|
|
1446
|
+
* 获取存储管理器实例
|
|
1447
|
+
* @returns StorageManager 实例
|
|
1448
|
+
*/
|
|
1449
|
+
getStorageManager() {
|
|
1450
|
+
return this.storageManager;
|
|
1451
|
+
}
|
|
1452
|
+
/**
|
|
1453
|
+
* 触发状态变更通知
|
|
1454
|
+
* @param affectedSlot - (可选) 受影响的插槽位置
|
|
1455
|
+
*/
|
|
1456
|
+
notify(affectedSlot) {
|
|
1457
|
+
(0, import_react2.startTransition)(() => {
|
|
1458
|
+
if (affectedSlot) {
|
|
1459
|
+
this.memoizedExtensions.delete(String(affectedSlot));
|
|
1460
|
+
} else {
|
|
1461
|
+
this.memoizedExtensions.clear();
|
|
1462
|
+
this.memoizedRoutes = null;
|
|
1463
|
+
}
|
|
1464
|
+
this.listeners.forEach((listener) => listener());
|
|
1465
|
+
if (affectedSlot) {
|
|
1466
|
+
this.slotListeners.get(String(affectedSlot))?.forEach((listener) => listener());
|
|
1467
|
+
} else {
|
|
1468
|
+
this.slotListeners.forEach((set) => set.forEach((listener) => listener()));
|
|
1469
|
+
}
|
|
1470
|
+
});
|
|
1471
|
+
}
|
|
1472
|
+
/**
|
|
1473
|
+
* 获取所有已注册的插件列表
|
|
1474
|
+
* @description 结果会根据插件类型优先级和用户自定义排序进行排序
|
|
1475
|
+
* @returns 排序后的插件数组
|
|
1476
|
+
*/
|
|
1477
|
+
getPlugins() {
|
|
1478
|
+
const typePriority = {
|
|
1479
|
+
system: 0,
|
|
1480
|
+
theme: 1,
|
|
1481
|
+
renderer: 2,
|
|
1482
|
+
functional: 3,
|
|
1483
|
+
business: 4,
|
|
1484
|
+
view: 5
|
|
1485
|
+
};
|
|
1486
|
+
return Array.from(this.plugins.values()).sort((a, b) => {
|
|
1487
|
+
const priorityA = typePriority[a.metadata.type] ?? 99;
|
|
1488
|
+
const priorityB = typePriority[b.metadata.type] ?? 99;
|
|
1489
|
+
if (priorityA !== priorityB) {
|
|
1490
|
+
return priorityA - priorityB;
|
|
1491
|
+
}
|
|
1492
|
+
const stateA = this.pluginStates[a.id] || { order: 0 };
|
|
1493
|
+
const stateB = this.pluginStates[b.id] || { order: 0 };
|
|
1494
|
+
return stateA.order - stateB.order;
|
|
1495
|
+
});
|
|
1496
|
+
}
|
|
1497
|
+
/**
|
|
1498
|
+
* 获取指定插件的状态信息
|
|
1499
|
+
* @param pluginId - 插件 ID
|
|
1500
|
+
* @returns 包含启用状态和排序值的对象
|
|
1501
|
+
*/
|
|
1502
|
+
getPluginState(pluginId) {
|
|
1503
|
+
return this.pluginStates[pluginId] || { enabled: true, order: 0 };
|
|
1504
|
+
}
|
|
1505
|
+
/**
|
|
1506
|
+
* 检查指定插件是否处于启用状态
|
|
1507
|
+
* @param pluginId - 插件 ID
|
|
1508
|
+
* @returns 是否启用
|
|
1509
|
+
*/
|
|
1510
|
+
isPluginEnabled(pluginId) {
|
|
1511
|
+
const state = this.pluginStates[pluginId];
|
|
1512
|
+
return state ? state.enabled : true;
|
|
1513
|
+
}
|
|
1514
|
+
/**
|
|
1515
|
+
* 切换插件的启用/禁用状态
|
|
1516
|
+
* @description 禁用插件会立即触发其卸载生命周期并销毁运行时。
|
|
1517
|
+
* @param pluginId - 插件 ID
|
|
1518
|
+
* @param enabled - 目标状态
|
|
1519
|
+
*/
|
|
1520
|
+
togglePlugin(pluginId, enabled) {
|
|
1521
|
+
const state = this.pluginStates[pluginId] || { enabled: true, order: 0 };
|
|
1522
|
+
this.pluginStates[pluginId] = { ...state, enabled };
|
|
1523
|
+
if (!enabled) {
|
|
1524
|
+
const runtime = this.runtimes.get(pluginId);
|
|
1525
|
+
if (runtime) {
|
|
1526
|
+
runtime.unmount();
|
|
1527
|
+
this.runtimes.delete(pluginId);
|
|
1528
|
+
}
|
|
1529
|
+
} else {
|
|
1530
|
+
if (this.sharedContext) {
|
|
1531
|
+
const plugin = this.plugins.get(pluginId);
|
|
1532
|
+
if (plugin) {
|
|
1533
|
+
try {
|
|
1534
|
+
const runtime = new PluginRuntime(plugin, this.sharedContext, this.storageManager);
|
|
1535
|
+
this.runtimes.set(pluginId, runtime);
|
|
1536
|
+
runtime.mount();
|
|
1537
|
+
} catch (e) {
|
|
1538
|
+
logger6.error(`\u542F\u7528\u63D2\u4EF6 ${pluginId} \u5931\u8D25:`, e);
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
this.notify();
|
|
1544
|
+
}
|
|
1545
|
+
/**
|
|
1546
|
+
* 设置插件的显示排序权重
|
|
1547
|
+
* @param pluginId - 插件 ID
|
|
1548
|
+
* @param order - 排序权重值
|
|
1549
|
+
*/
|
|
1550
|
+
setPluginOrder(pluginId, order) {
|
|
1551
|
+
const state = this.pluginStates[pluginId] || { enabled: true, order: 0 };
|
|
1552
|
+
this.pluginStates[pluginId] = { ...state, order };
|
|
1553
|
+
this.notify();
|
|
1554
|
+
}
|
|
1555
|
+
/**
|
|
1556
|
+
* 获取指定插件的运行时状态
|
|
1557
|
+
* @param pluginId - 插件 ID
|
|
1558
|
+
* @returns 'error' | 'mounted' | 'loaded' | 'initial'
|
|
1559
|
+
*/
|
|
1560
|
+
getPluginRuntimeStatus(pluginId) {
|
|
1561
|
+
const runtime = this.runtimes.get(pluginId);
|
|
1562
|
+
return runtime ? runtime.status : "initial";
|
|
1563
|
+
}
|
|
1564
|
+
/**
|
|
1565
|
+
* 获取指定插件的运行时错误
|
|
1566
|
+
* @param pluginId - 插件 ID
|
|
1567
|
+
* @returns Error 对象或 null
|
|
1568
|
+
*/
|
|
1569
|
+
getPluginError(pluginId) {
|
|
1570
|
+
const runtime = this.runtimes.get(pluginId);
|
|
1571
|
+
return runtime ? runtime.getError() : null;
|
|
1572
|
+
}
|
|
1573
|
+
/**
|
|
1574
|
+
* 报告插件运行时错误 (通常由 ErrorBoundary 调用)
|
|
1575
|
+
* @param pluginId - 插件 ID
|
|
1576
|
+
* @param error - 错误对象
|
|
1577
|
+
*/
|
|
1578
|
+
reportPluginError(pluginId, error) {
|
|
1579
|
+
const runtime = this.runtimes.get(pluginId);
|
|
1580
|
+
if (runtime) {
|
|
1581
|
+
runtime._setError?.(error);
|
|
1582
|
+
this.notify();
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
/**
|
|
1586
|
+
* 获取插件的完整能力声明
|
|
1587
|
+
* @param pluginId - 插件 ID
|
|
1588
|
+
* @returns 能力对象
|
|
1589
|
+
*/
|
|
1590
|
+
getUnifiedCapabilities(pluginId) {
|
|
1591
|
+
const plugin = this.plugins.get(pluginId);
|
|
1592
|
+
return plugin?.metadata.capabilities || {};
|
|
1593
|
+
}
|
|
1594
|
+
/**
|
|
1595
|
+
* 更新指定插件的某项配置
|
|
1596
|
+
* @description 该操作会同步更新内存中的配置、持久化到存储并触发全局事件通知。
|
|
1597
|
+
* @param pluginId - 插件 ID
|
|
1598
|
+
* @param key - 配置键名
|
|
1599
|
+
* @param value - 新的配置值
|
|
1600
|
+
*/
|
|
1601
|
+
updatePluginConfig(pluginId, key, value) {
|
|
1602
|
+
const currentConfig = configManager.get(pluginId) || {};
|
|
1603
|
+
currentConfig[key] = value;
|
|
1604
|
+
configManager.set(pluginId, currentConfig);
|
|
1605
|
+
try {
|
|
1606
|
+
this.storageManager.getContextStorage(pluginId).set(key, value);
|
|
1607
|
+
} catch (e) {
|
|
1608
|
+
logger6.warn("\u4FDD\u5B58\u914D\u7F6E\u5230\u5B58\u50A8\u5931\u8D25", e);
|
|
1609
|
+
}
|
|
1610
|
+
this.eventBus.emit("config:changed", { pluginId, key, value });
|
|
1611
|
+
}
|
|
1612
|
+
/**
|
|
1613
|
+
* 获取指定插件的某项配置值
|
|
1614
|
+
* @param pluginId - 插件 ID
|
|
1615
|
+
* @param key - 配置键名
|
|
1616
|
+
* @returns 配置值
|
|
1617
|
+
*/
|
|
1618
|
+
getPluginConfig(pluginId, key) {
|
|
1619
|
+
const config = configManager.get(pluginId);
|
|
1620
|
+
return config ? config[key] : void 0;
|
|
1621
|
+
}
|
|
1622
|
+
/**
|
|
1623
|
+
* 获取系统全局配置 (非插件特定)
|
|
1624
|
+
* @param key - 系统配置键名
|
|
1625
|
+
* @returns 配置值
|
|
1626
|
+
*/
|
|
1627
|
+
getSystemConfig(key) {
|
|
1628
|
+
const systemConfig = configManager.get("system");
|
|
1629
|
+
return systemConfig ? systemConfig[key] : void 0;
|
|
1630
|
+
}
|
|
1631
|
+
/**
|
|
1632
|
+
* 获取由插件注册的服务实例
|
|
1633
|
+
* @template T 服务接口类型
|
|
1634
|
+
* @param name - 服务注册名称
|
|
1635
|
+
* @returns 服务实例或 undefined
|
|
1636
|
+
*/
|
|
1637
|
+
getService(name) {
|
|
1638
|
+
return serviceRegistry.get(name);
|
|
1639
|
+
}
|
|
1640
|
+
/**
|
|
1641
|
+
* 获取指定插槽位置的所有已启用插件的扩展
|
|
1642
|
+
* @param slot - 插槽位置标识
|
|
1643
|
+
* @returns 排序后的扩展配置数组
|
|
1644
|
+
*/
|
|
1645
|
+
getExtensions(slot) {
|
|
1646
|
+
const slotStr = String(slot);
|
|
1647
|
+
if (this.memoizedExtensions.has(slotStr)) {
|
|
1648
|
+
return this.memoizedExtensions.get(slotStr);
|
|
1649
|
+
}
|
|
1650
|
+
const targetSlot = slot;
|
|
1651
|
+
let extensions = this.extensions.get(targetSlot) || [];
|
|
1652
|
+
extensions = extensions.filter((ext) => {
|
|
1653
|
+
const pluginId = ext._pluginId;
|
|
1654
|
+
return !pluginId || this.isPluginEnabled(pluginId);
|
|
1655
|
+
});
|
|
1656
|
+
const sortedExtensions = extensions.sort((a, b) => (a.order || 0) - (b.order || 0));
|
|
1657
|
+
this.memoizedExtensions.set(slotStr, sortedExtensions);
|
|
1658
|
+
return sortedExtensions;
|
|
1659
|
+
}
|
|
1660
|
+
/**
|
|
1661
|
+
* 获取所有已启用插件注册的路由配置
|
|
1662
|
+
* @returns 增强后的路由配置数组
|
|
1663
|
+
*/
|
|
1664
|
+
getRoutes() {
|
|
1665
|
+
if (this.memoizedRoutes) {
|
|
1666
|
+
return this.memoizedRoutes;
|
|
1667
|
+
}
|
|
1668
|
+
const activeRoutes = [];
|
|
1669
|
+
this.getPlugins().forEach((plugin) => {
|
|
1670
|
+
if (this.isPluginEnabled(plugin.id) && plugin.metadata.routes) {
|
|
1671
|
+
const config = configManager.get(plugin.id) || {};
|
|
1672
|
+
plugin.metadata.routes.forEach((route) => {
|
|
1673
|
+
activeRoutes.push({
|
|
1674
|
+
...route,
|
|
1675
|
+
meta: {
|
|
1676
|
+
...route.meta,
|
|
1677
|
+
pluginId: plugin.id,
|
|
1678
|
+
config
|
|
1679
|
+
}
|
|
1680
|
+
});
|
|
1681
|
+
});
|
|
1682
|
+
}
|
|
1683
|
+
});
|
|
1684
|
+
this.memoizedRoutes = activeRoutes;
|
|
1685
|
+
return activeRoutes;
|
|
1686
|
+
}
|
|
1687
|
+
/**
|
|
1688
|
+
* 注册一个新插件到管理器中
|
|
1689
|
+
* @description 此阶段会执行元数据校验、存储 Schema 注册、配置合并及扩展点收集。
|
|
1690
|
+
* @param plugin - 插件对象
|
|
1691
|
+
* @param notify - 是否在注册完成后触发状态变更通知
|
|
1692
|
+
*/
|
|
1693
|
+
register(plugin, notify = true) {
|
|
1694
|
+
if (!this.validatePlugin(plugin)) {
|
|
1695
|
+
logger6.error(`\u63D2\u4EF6\u6CE8\u518C\u5931\u8D25: ${plugin?.id || "\u672A\u77E5"}`);
|
|
1696
|
+
return;
|
|
1697
|
+
}
|
|
1698
|
+
if (this.plugins.has(plugin.id)) {
|
|
1699
|
+
return;
|
|
1700
|
+
}
|
|
1701
|
+
const storageSchema = [
|
|
1702
|
+
...plugin.metadata.storage || [],
|
|
1703
|
+
...plugin.metadata.configuration?.map((c) => ({
|
|
1704
|
+
key: c.key,
|
|
1705
|
+
// 将 configuration 的 type 映射为 storage 支持的类型
|
|
1706
|
+
type: c.type === "select" ? "string" : c.type,
|
|
1707
|
+
label: c.label,
|
|
1708
|
+
description: c.description,
|
|
1709
|
+
default: c.default,
|
|
1710
|
+
scope: "plugin"
|
|
1711
|
+
})) || []
|
|
1712
|
+
];
|
|
1713
|
+
if (storageSchema.length > 0) {
|
|
1714
|
+
this.storageManager.registerSchema(plugin.id, storageSchema);
|
|
1715
|
+
}
|
|
1716
|
+
if (!this.pluginStates[plugin.id]) {
|
|
1717
|
+
this.pluginStates[plugin.id] = { enabled: true, order: 0 };
|
|
1718
|
+
}
|
|
1719
|
+
const metadataDefaults = {};
|
|
1720
|
+
const userOverrides = {};
|
|
1721
|
+
const pluginStorage = this.storageManager.getPluginStorage(plugin.id);
|
|
1722
|
+
if (plugin.metadata.configuration) {
|
|
1723
|
+
plugin.metadata.configuration.forEach((item) => {
|
|
1724
|
+
if (item.default !== void 0) {
|
|
1725
|
+
metadataDefaults[item.key] = item.default;
|
|
1726
|
+
}
|
|
1727
|
+
try {
|
|
1728
|
+
const saved = pluginStorage.getItem(item.key);
|
|
1729
|
+
if (saved !== null) {
|
|
1730
|
+
userOverrides[item.key] = JSON.parse(saved);
|
|
1731
|
+
}
|
|
1732
|
+
} catch (e) {
|
|
1733
|
+
}
|
|
1734
|
+
});
|
|
1735
|
+
}
|
|
1736
|
+
const mergedConfig = {
|
|
1737
|
+
...metadataDefaults,
|
|
1738
|
+
...plugin.defaultConfig,
|
|
1739
|
+
...configManager.get(plugin.id) || {},
|
|
1740
|
+
...userOverrides
|
|
1741
|
+
};
|
|
1742
|
+
configManager.set(plugin.id, mergedConfig);
|
|
1743
|
+
switch (plugin.metadata.type) {
|
|
1744
|
+
case "business":
|
|
1745
|
+
this.handleBusinessPlugin(plugin);
|
|
1746
|
+
break;
|
|
1747
|
+
case "functional":
|
|
1748
|
+
this.handleFunctionalPlugin(plugin);
|
|
1749
|
+
break;
|
|
1750
|
+
case "view":
|
|
1751
|
+
this.handleViewPlugin(plugin);
|
|
1752
|
+
break;
|
|
1753
|
+
case "theme":
|
|
1754
|
+
this.handleThemePlugin(plugin);
|
|
1755
|
+
break;
|
|
1756
|
+
case "system":
|
|
1757
|
+
this.handleSystemPlugin(plugin);
|
|
1758
|
+
break;
|
|
1759
|
+
case "renderer":
|
|
1760
|
+
break;
|
|
1761
|
+
default:
|
|
1762
|
+
logger6.warn(`\u63D2\u4EF6 ${plugin.id} \u7C7B\u578B\u672A\u77E5: ${plugin.metadata.type}`);
|
|
1763
|
+
break;
|
|
1764
|
+
}
|
|
1765
|
+
if (plugin.metadata.routes && plugin.metadata.routes.length > 0) {
|
|
1766
|
+
logger6.info(`\u5DF2\u4ECE\u63D2\u4EF6 ${plugin.id} \u6536\u96C6\u8DEF\u7531:`, plugin.metadata.routes);
|
|
1767
|
+
}
|
|
1768
|
+
if (plugin.metadata.extensions && plugin.metadata.extensions.length > 0) {
|
|
1769
|
+
plugin.metadata.extensions.forEach((ext) => {
|
|
1770
|
+
const list = this.extensions.get(ext.slot) || [];
|
|
1771
|
+
list.push({ ...ext, _pluginId: plugin.id });
|
|
1772
|
+
this.extensions.set(ext.slot, list);
|
|
1773
|
+
});
|
|
1774
|
+
logger6.info(`\u5DF2\u4ECE\u63D2\u4EF6 ${plugin.id} \u6536\u96C6\u6269\u5C55\u70B9`);
|
|
1775
|
+
}
|
|
1776
|
+
this.plugins.set(plugin.id, plugin);
|
|
1777
|
+
logger6.info(`\u63D2\u4EF6 ${plugin.id} \u5DF2\u6CE8\u518C\u4E3A ${plugin.metadata.type}\u3002`);
|
|
1778
|
+
if (notify) {
|
|
1779
|
+
this.notify();
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
/**
|
|
1783
|
+
* 初始化所有插件
|
|
1784
|
+
* @param sharedContext 共享上下文
|
|
1785
|
+
*/
|
|
1786
|
+
async initPlugins(sharedContext = {}) {
|
|
1787
|
+
this.sharedContext = {
|
|
1788
|
+
...sharedContext,
|
|
1789
|
+
events: this.eventBus
|
|
1790
|
+
};
|
|
1791
|
+
this.plugins.forEach((plugin) => {
|
|
1792
|
+
if (!this.isPluginEnabled(plugin.id)) return;
|
|
1793
|
+
if (!this.runtimes.has(plugin.id)) {
|
|
1794
|
+
const runtime = new PluginRuntime(plugin, this.sharedContext, this.storageManager);
|
|
1795
|
+
this.runtimes.set(plugin.id, runtime);
|
|
1796
|
+
}
|
|
1797
|
+
});
|
|
1798
|
+
const sortedPluginIds = this.getSortedPluginIds();
|
|
1799
|
+
const missingDeps = /* @__PURE__ */ new Map();
|
|
1800
|
+
const idsToLoad = new Set(sortedPluginIds);
|
|
1801
|
+
for (const id of sortedPluginIds) {
|
|
1802
|
+
const plugin = this.plugins.get(id);
|
|
1803
|
+
if (plugin?.metadata.dependencies) {
|
|
1804
|
+
const missing = plugin.metadata.dependencies.filter((depId) => !this.runtimes.has(depId));
|
|
1805
|
+
if (missing.length > 0) {
|
|
1806
|
+
missingDeps.set(id, missing);
|
|
1807
|
+
idsToLoad.delete(id);
|
|
1808
|
+
this.runtimes.delete(id);
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
if (missingDeps.size > 0) {
|
|
1813
|
+
missingDeps.forEach((deps, id) => {
|
|
1814
|
+
logger6.error(`\u63D2\u4EF6 ${id} \u65E0\u6CD5\u52A0\u8F7D\uFF0C\u7F3A\u5931\u4F9D\u8D56: ${deps.join(", ")}`);
|
|
1815
|
+
});
|
|
1816
|
+
}
|
|
1817
|
+
for (const id of sortedPluginIds) {
|
|
1818
|
+
if (!idsToLoad.has(id)) continue;
|
|
1819
|
+
const runtime = this.runtimes.get(id);
|
|
1820
|
+
if (runtime) {
|
|
1821
|
+
try {
|
|
1822
|
+
logger6.info(`[PluginManager] invoking onLoad for ${id}`);
|
|
1823
|
+
await runtime.load();
|
|
1824
|
+
logger6.info(`[PluginManager] onLoad completed for ${id}`);
|
|
1825
|
+
} catch (e) {
|
|
1826
|
+
logger6.error(`\u63D2\u4EF6 ${id} \u52A0\u8F7D\u5931\u8D25:`, e);
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
for (const id of sortedPluginIds) {
|
|
1831
|
+
const runtime = this.runtimes.get(id);
|
|
1832
|
+
if (runtime) {
|
|
1833
|
+
try {
|
|
1834
|
+
await runtime.mount();
|
|
1835
|
+
} catch (e) {
|
|
1836
|
+
logger6.error(`\u63D2\u4EF6 ${id} \u6302\u8F7D\u5931\u8D25:`, e);
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
/**
|
|
1842
|
+
* 获取排序后的插件 ID 列表 (处理依赖)
|
|
1843
|
+
*/
|
|
1844
|
+
getSortedPluginIds() {
|
|
1845
|
+
const ids = Array.from(this.runtimes.keys());
|
|
1846
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1847
|
+
const sorted = [];
|
|
1848
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
1849
|
+
const visit = (id) => {
|
|
1850
|
+
if (visited.has(id)) return;
|
|
1851
|
+
if (visiting.has(id)) {
|
|
1852
|
+
logger6.error(`\u5FAA\u73AF\u4F9D\u8D56\u68C0\u6D4B\u5230: ${id}`);
|
|
1853
|
+
return;
|
|
1854
|
+
}
|
|
1855
|
+
visiting.add(id);
|
|
1856
|
+
const plugin = this.plugins.get(id);
|
|
1857
|
+
if (plugin?.metadata.dependencies) {
|
|
1858
|
+
plugin.metadata.dependencies.forEach((depId) => {
|
|
1859
|
+
if (this.runtimes.has(depId)) {
|
|
1860
|
+
visit(depId);
|
|
1861
|
+
}
|
|
1862
|
+
});
|
|
1863
|
+
}
|
|
1864
|
+
visiting.delete(id);
|
|
1865
|
+
visited.add(id);
|
|
1866
|
+
sorted.push(id);
|
|
1867
|
+
};
|
|
1868
|
+
const priorityMap = {
|
|
1869
|
+
"system": 100,
|
|
1870
|
+
"functional": 50,
|
|
1871
|
+
"business": 10
|
|
1872
|
+
};
|
|
1873
|
+
const sortedIdsByType = [...ids].sort((a, b) => {
|
|
1874
|
+
const pA = priorityMap[this.plugins.get(a)?.metadata.type || ""] || 0;
|
|
1875
|
+
const pB = priorityMap[this.plugins.get(b)?.metadata.type || ""] || 0;
|
|
1876
|
+
return pB - pA;
|
|
1877
|
+
});
|
|
1878
|
+
sortedIdsByType.forEach((id) => visit(id));
|
|
1879
|
+
return sorted;
|
|
1880
|
+
}
|
|
1881
|
+
/**
|
|
1882
|
+
* 加载插件列表
|
|
1883
|
+
* @param configs 插件配置
|
|
1884
|
+
* @param registry 插件注册表 (动态导入函数)
|
|
1885
|
+
*/
|
|
1886
|
+
async loadPlugins(configs, registry) {
|
|
1887
|
+
logger6.info("\u5F00\u59CB\u52A0\u8F7D\u63D2\u4EF6...");
|
|
1888
|
+
const localLoadPromises = Object.entries(registry).map(async ([registryId, importFn]) => {
|
|
1889
|
+
try {
|
|
1890
|
+
const module2 = await importFn();
|
|
1891
|
+
const config = configs[registryId];
|
|
1892
|
+
const plugin = this.instantiatePlugin(registryId, module2, config);
|
|
1893
|
+
if (plugin && config) {
|
|
1894
|
+
configManager.set(plugin.id, config);
|
|
1895
|
+
}
|
|
1896
|
+
return plugin;
|
|
1897
|
+
} catch (e) {
|
|
1898
|
+
logger6.error(`\u52A0\u8F7D\u672C\u5730\u63D2\u4EF6\u6A21\u5757 ${registryId} \u5931\u8D25:`, e);
|
|
1899
|
+
return null;
|
|
1900
|
+
}
|
|
1901
|
+
});
|
|
1902
|
+
const onlineLoadPromises = Object.entries(configs).filter(([id, config]) => config.url && !registry[id]).map(async ([pluginId, config]) => {
|
|
1903
|
+
try {
|
|
1904
|
+
const plugin = await this.loadRemotePlugin(pluginId, config.url, config);
|
|
1905
|
+
if (plugin && config) {
|
|
1906
|
+
configManager.set(plugin.id, config);
|
|
1907
|
+
}
|
|
1908
|
+
return plugin;
|
|
1909
|
+
} catch (e) {
|
|
1910
|
+
logger6.error(`\u52A0\u8F7D\u5728\u7EBF\u63D2\u4EF6 ${pluginId} \u5931\u8D25:`, e);
|
|
1911
|
+
return null;
|
|
1912
|
+
}
|
|
1913
|
+
});
|
|
1914
|
+
const loadedPlugins = await Promise.all([...localLoadPromises, ...onlineLoadPromises]);
|
|
1915
|
+
loadedPlugins.forEach((plugin) => {
|
|
1916
|
+
if (plugin) {
|
|
1917
|
+
this.register(plugin, false);
|
|
1918
|
+
}
|
|
1919
|
+
});
|
|
1920
|
+
this.notify();
|
|
1921
|
+
logger6.info(`\u63D2\u4EF6\u52A0\u8F7D\u5B8C\u6210\uFF0C\u5171\u52A0\u8F7D ${this.plugins.size} \u4E2A\u63D2\u4EF6`);
|
|
1922
|
+
}
|
|
1923
|
+
/**
|
|
1924
|
+
* 加载远程插件
|
|
1925
|
+
* @param pluginId 插件 ID
|
|
1926
|
+
* @param url 远程 URL
|
|
1927
|
+
* @param config 插件配置
|
|
1928
|
+
*/
|
|
1929
|
+
async loadRemotePlugin(pluginId, url, config) {
|
|
1930
|
+
logger6.info(`\u6B63\u5728\u4ECE ${url} \u52A0\u8F7D\u8FDC\u7A0B\u63D2\u4EF6 ${pluginId}...`);
|
|
1931
|
+
if (config?.format === "iife") {
|
|
1932
|
+
return this.loadIIFEPlugin(pluginId, url, config);
|
|
1933
|
+
}
|
|
1934
|
+
try {
|
|
1935
|
+
const dynamicImport = new Function("specifier", "return import(specifier)");
|
|
1936
|
+
const module2 = await dynamicImport(url);
|
|
1937
|
+
return this.instantiatePlugin(pluginId, module2, config);
|
|
1938
|
+
} catch (e) {
|
|
1939
|
+
logger6.warn(`ESM \u52A0\u8F7D\u5931\u8D25\uFF0C\u5C1D\u8BD5 IIFE \u52A0\u8F7D: ${pluginId}`);
|
|
1940
|
+
return this.loadIIFEPlugin(pluginId, url, config);
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1943
|
+
/**
|
|
1944
|
+
* IIFE 模式加载插件
|
|
1945
|
+
*/
|
|
1946
|
+
loadIIFEPlugin(pluginId, url, config) {
|
|
1947
|
+
return new Promise((resolve, reject) => {
|
|
1948
|
+
const script = document.createElement("script");
|
|
1949
|
+
script.src = url;
|
|
1950
|
+
script.onload = () => {
|
|
1951
|
+
const globalName = pluginId.replace(/[^a-zA-Z0-9]/g, "_");
|
|
1952
|
+
const pluginModule = window[globalName];
|
|
1953
|
+
if (pluginModule) {
|
|
1954
|
+
resolve(this.instantiatePlugin(pluginId, pluginModule, config));
|
|
1955
|
+
} else {
|
|
1956
|
+
reject(new Error(`\u8FDC\u7A0B\u63D2\u4EF6 ${pluginId} \u52A0\u8F7D\u540E\u672A\u627E\u5230\u5168\u5C40\u53D8\u91CF ${globalName}`));
|
|
1957
|
+
}
|
|
1958
|
+
};
|
|
1959
|
+
script.onerror = () => reject(new Error(`\u8FDC\u7A0B\u63D2\u4EF6 ${pluginId} \u52A0\u8F7D\u5931\u8D25: ${url}`));
|
|
1960
|
+
document.head.appendChild(script);
|
|
1961
|
+
});
|
|
1962
|
+
}
|
|
1963
|
+
/**
|
|
1964
|
+
* 实例化插件
|
|
1965
|
+
*/
|
|
1966
|
+
instantiatePlugin(pluginId, module2, config) {
|
|
1967
|
+
let PluginClass = module2.default;
|
|
1968
|
+
if (!PluginClass) {
|
|
1969
|
+
const key = Object.keys(module2).find((k) => k.endsWith("Plugin"));
|
|
1970
|
+
if (key) PluginClass = module2[key];
|
|
1971
|
+
}
|
|
1972
|
+
if (!PluginClass && typeof module2 === "object") {
|
|
1973
|
+
if (module2.id && module2.metadata) {
|
|
1974
|
+
PluginClass = module2;
|
|
1975
|
+
}
|
|
1976
|
+
}
|
|
1977
|
+
if (PluginClass) {
|
|
1978
|
+
const isClass = typeof PluginClass === "function" && PluginClass.prototype;
|
|
1979
|
+
if (isClass) {
|
|
1980
|
+
logger6.warn(`\u63D2\u4EF6 ${pluginId} \u4F7F\u7528\u4E86\u7C7B\u5B9A\u4E49\u6A21\u5F0F\u3002\u5EFA\u8BAE\u7EDF\u4E00\u4F7F\u7528 definePlugin() \u5DE5\u5382\u6A21\u5F0F\u4EE5\u6D88\u9664\u6B67\u4E49\u5E76\u7B80\u5316\u4EE3\u7801\u3002`);
|
|
1981
|
+
}
|
|
1982
|
+
const pluginInstance = typeof PluginClass === "function" ? new PluginClass() : PluginClass;
|
|
1983
|
+
const isFilePath = pluginId.includes("/") && (pluginId.includes(".ts") || pluginId.includes(".tsx"));
|
|
1984
|
+
if (!isFilePath && pluginId && pluginInstance.metadata) {
|
|
1985
|
+
if (pluginInstance.metadata.id !== pluginId) {
|
|
1986
|
+
pluginInstance.metadata.id = pluginId;
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
if (!pluginInstance.id && pluginInstance.metadata?.id) {
|
|
1990
|
+
try {
|
|
1991
|
+
pluginInstance.id = pluginInstance.metadata.id;
|
|
1992
|
+
} catch (e) {
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
if (config) {
|
|
1996
|
+
pluginInstance.defaultConfig = { ...pluginInstance.defaultConfig, ...config };
|
|
1997
|
+
}
|
|
1998
|
+
return pluginInstance;
|
|
1999
|
+
}
|
|
2000
|
+
logger6.warn(`\u6A21\u5757 ${pluginId} \u672A\u5BFC\u51FA\u6709\u6548\u7684\u63D2\u4EF6\u5165\u53E3`);
|
|
2001
|
+
return null;
|
|
2002
|
+
}
|
|
2003
|
+
// --- Private Handlers ---
|
|
2004
|
+
validatePlugin(plugin) {
|
|
2005
|
+
if (!plugin.id) return false;
|
|
2006
|
+
if (!plugin.metadata) return false;
|
|
2007
|
+
return true;
|
|
2008
|
+
}
|
|
2009
|
+
handleBusinessPlugin(plugin) {
|
|
2010
|
+
}
|
|
2011
|
+
handleFunctionalPlugin(plugin) {
|
|
2012
|
+
}
|
|
2013
|
+
handleViewPlugin(plugin) {
|
|
2014
|
+
}
|
|
2015
|
+
handleThemePlugin(plugin) {
|
|
2016
|
+
}
|
|
2017
|
+
handleSystemPlugin(plugin) {
|
|
2018
|
+
}
|
|
2019
|
+
};
|
|
2020
|
+
var pluginManager = new PluginManager(new LocalStorageAdapter());
|
|
2021
|
+
|
|
2022
|
+
// src/components/PluginErrorBoundary.tsx
|
|
2023
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
2024
|
+
var logger7 = createLogger("PluginErrorBoundary");
|
|
2025
|
+
var PluginErrorBoundary = class extends import_react3.Component {
|
|
2026
|
+
constructor(props) {
|
|
2027
|
+
super(props);
|
|
2028
|
+
this.state = { hasError: false, error: null };
|
|
2029
|
+
}
|
|
2030
|
+
/**
|
|
2031
|
+
* 从错误中派生状态
|
|
2032
|
+
* @param error - 捕获到的错误
|
|
2033
|
+
*/
|
|
2034
|
+
static getDerivedStateFromError(error) {
|
|
2035
|
+
if (error && typeof error.then === "function") {
|
|
2036
|
+
return { hasError: false, error: null };
|
|
2037
|
+
}
|
|
2038
|
+
return { hasError: true, error: error instanceof Error ? error : new Error(String(error)) };
|
|
2039
|
+
}
|
|
2040
|
+
/**
|
|
2041
|
+
* 捕获到错误后的回调
|
|
2042
|
+
* @param error - 错误对象
|
|
2043
|
+
* @param errorInfo - 错误堆栈信息
|
|
2044
|
+
*/
|
|
2045
|
+
componentDidCatch(error, errorInfo) {
|
|
2046
|
+
if (error && typeof error.then === "function") {
|
|
2047
|
+
return;
|
|
2048
|
+
}
|
|
2049
|
+
logger7.error(`\u63D2\u4EF6 ${this.props.pluginId || "\u672A\u77E5"} \u6E32\u67D3\u53D1\u751F\u9519\u8BEF:`, error, errorInfo);
|
|
2050
|
+
if (this.props.pluginId) {
|
|
2051
|
+
console.warn(`[PluginError] \u63D2\u4EF6 "${this.props.pluginId}" \u6E32\u67D3\u5931\u8D25\u3002\u60A8\u53EF\u4EE5\u5728\u63D2\u4EF6\u7BA1\u7406\u9762\u677F\u67E5\u770B\u8BE6\u7EC6\u4FE1\u606F\u3002`);
|
|
2052
|
+
pluginManager.reportPluginError(this.props.pluginId, error);
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
/**
|
|
2056
|
+
* 重置错误状态,尝试重新渲染
|
|
2057
|
+
*/
|
|
2058
|
+
handleRetry = () => {
|
|
2059
|
+
this.setState({ hasError: false, error: null });
|
|
2060
|
+
};
|
|
2061
|
+
render() {
|
|
2062
|
+
if (this.state.hasError) {
|
|
2063
|
+
if (this.props.fallback) {
|
|
2064
|
+
return this.props.fallback;
|
|
2065
|
+
}
|
|
2066
|
+
if (this.props.silent) {
|
|
2067
|
+
return null;
|
|
2068
|
+
}
|
|
2069
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
2070
|
+
"div",
|
|
2071
|
+
{
|
|
2072
|
+
className: "plugin-error-placeholder hidden",
|
|
2073
|
+
"data-plugin-id": this.props.pluginId,
|
|
2074
|
+
"data-error": this.state.error?.message
|
|
2075
|
+
}
|
|
2076
|
+
);
|
|
2077
|
+
}
|
|
2078
|
+
return this.props.children;
|
|
2079
|
+
}
|
|
2080
|
+
};
|
|
2081
|
+
|
|
2082
|
+
// src/components/PluginSlot.tsx
|
|
2083
|
+
var import_react4 = require("react");
|
|
2084
|
+
|
|
2085
|
+
// src/components/SlotSkeletons.tsx
|
|
2086
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
2087
|
+
var SidebarIconSkeleton = ({ expanded = false }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: `flex items-center transition-all duration-300 relative
|
|
2088
|
+
${expanded ? "w-full" : "w-12 justify-center"} px-3 h-11 rounded-xl`, children: [
|
|
2089
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "w-6 h-6 bg-slate-200 dark:bg-white/10 rounded-lg shrink-0 animate-pulse" }),
|
|
2090
|
+
expanded && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "ml-3 flex-1 h-4 bg-slate-200 dark:bg-white/10 rounded animate-pulse" })
|
|
2091
|
+
] });
|
|
2092
|
+
var StatusBarItemSkeleton = () => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "h-4 w-16 bg-slate-200 dark:bg-white/10 rounded animate-pulse" });
|
|
2093
|
+
var AvatarSkeleton = () => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "w-10 h-10 rounded-full bg-slate-200 dark:bg-white/10 animate-pulse" });
|
|
2094
|
+
var BlockSkeleton = ({ className }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: `bg-slate-200 dark:bg-white/10 rounded animate-pulse ${className || "w-full h-full"}` });
|
|
2095
|
+
var SlotSkeletons = ({ slot, expanded }) => {
|
|
2096
|
+
if (slot.includes("sidebar")) {
|
|
2097
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SidebarIconSkeleton, { expanded });
|
|
2098
|
+
}
|
|
2099
|
+
if (slot.includes("status")) {
|
|
2100
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(StatusBarItemSkeleton, {});
|
|
2101
|
+
}
|
|
2102
|
+
if (slot.includes("avatar")) {
|
|
2103
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(AvatarSkeleton, {});
|
|
2104
|
+
}
|
|
2105
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(BlockSkeleton, {});
|
|
2106
|
+
};
|
|
2107
|
+
|
|
2108
|
+
// src/components/PluginSlot.tsx
|
|
2109
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
2110
|
+
var PluginSlot = ({
|
|
2111
|
+
slot,
|
|
2112
|
+
props = {},
|
|
2113
|
+
className = "",
|
|
2114
|
+
style,
|
|
2115
|
+
renderItem,
|
|
2116
|
+
skeleton,
|
|
2117
|
+
fallback
|
|
2118
|
+
}) => {
|
|
2119
|
+
const [, forceUpdate] = (0, import_react4.useState)({});
|
|
2120
|
+
(0, import_react4.useEffect)(() => {
|
|
2121
|
+
const unsubscribe = pluginManager.subscribe(() => {
|
|
2122
|
+
forceUpdate({});
|
|
2123
|
+
}, slot);
|
|
2124
|
+
return unsubscribe;
|
|
2125
|
+
}, [slot]);
|
|
2126
|
+
const extensions = pluginManager.getExtensions(slot);
|
|
2127
|
+
const systemConfig = pluginManager.getSystemConfig("title") ? {
|
|
2128
|
+
title: pluginManager.getSystemConfig("title"),
|
|
2129
|
+
logo: pluginManager.getSystemConfig("logo"),
|
|
2130
|
+
version: pluginManager.getSystemConfig("version")
|
|
2131
|
+
} : void 0;
|
|
2132
|
+
const mergedProps = (0, import_react4.useMemo)(() => ({
|
|
2133
|
+
...props,
|
|
2134
|
+
systemConfig
|
|
2135
|
+
}), [props, systemConfig]);
|
|
2136
|
+
const items = (0, import_react4.useMemo)(() => {
|
|
2137
|
+
return extensions.map((ext, index) => {
|
|
2138
|
+
const Component2 = ext.component;
|
|
2139
|
+
const key = ext.meta?.key || `${ext.slot}-${ext.order || 0}-${index}`;
|
|
2140
|
+
return {
|
|
2141
|
+
key,
|
|
2142
|
+
extension: ext,
|
|
2143
|
+
component: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PluginErrorBoundary, { pluginId: ext._pluginId, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react4.Suspense, { fallback: skeleton || /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(SlotSkeletons, { slot }), children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Component2, { ...mergedProps }) }) }, key)
|
|
2144
|
+
};
|
|
2145
|
+
});
|
|
2146
|
+
}, [extensions, mergedProps]);
|
|
2147
|
+
if (items.length === 0) {
|
|
2148
|
+
if (fallback) {
|
|
2149
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: fallback });
|
|
2150
|
+
}
|
|
2151
|
+
if (skeleton) {
|
|
2152
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: `plugin-slot plugin-slot-${slot} plugin-slot-skeleton ${className || ""}`, style, children: skeleton });
|
|
2153
|
+
}
|
|
2154
|
+
return null;
|
|
2155
|
+
}
|
|
2156
|
+
if (items.length === 1 && slot === "root-layout" && !className && !style && !renderItem) {
|
|
2157
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: items[0].component });
|
|
2158
|
+
}
|
|
2159
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: `plugin-slot plugin-slot-${slot} ${className || ""}`, style, children: renderItem ? items.map((item, index) => renderItem(item, index)) : items.map((item) => item.component) });
|
|
2160
|
+
};
|
|
2161
|
+
|
|
2162
|
+
// src/domain/auto-loader.ts
|
|
2163
|
+
var logger8 = createLogger("AutoLoader");
|
|
2164
|
+
var DEFAULT_RULES = [
|
|
2165
|
+
{ pathSegment: "@chatbi-plugins", idPrefix: "@chatbi-v/plugin" },
|
|
2166
|
+
{ pathSegment: "@chatbi-apps", idPrefix: "@chatbi-v/app" },
|
|
2167
|
+
{ pathSegment: "packages/plugins", idPrefix: "@chatbi-v/plugin" },
|
|
2168
|
+
{ pathSegment: "packages/apps", idPrefix: "@chatbi-v/app" }
|
|
2169
|
+
];
|
|
2170
|
+
var resolvePluginRegistry = (options) => {
|
|
2171
|
+
const { modules, rules = DEFAULT_RULES } = options;
|
|
2172
|
+
const registry = {};
|
|
2173
|
+
const compiledRules = rules.map((rule) => {
|
|
2174
|
+
const escapedSegment = rule.pathSegment.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2175
|
+
return {
|
|
2176
|
+
...rule,
|
|
2177
|
+
regex: new RegExp(`${escapedSegment}/([^/]+)/src/index`)
|
|
2178
|
+
};
|
|
2179
|
+
});
|
|
2180
|
+
for (const path in modules) {
|
|
2181
|
+
try {
|
|
2182
|
+
let pluginId = null;
|
|
2183
|
+
for (const rule of compiledRules) {
|
|
2184
|
+
const match = path.match(rule.regex);
|
|
2185
|
+
if (match && match[1]) {
|
|
2186
|
+
pluginId = `${rule.idPrefix}-${match[1]}`;
|
|
2187
|
+
logger8.info(`\u89E3\u6790\u8DEF\u5F84\u6210\u529F: ${path} -> ${pluginId}`);
|
|
2188
|
+
break;
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
if (pluginId) {
|
|
2192
|
+
registry[pluginId] = modules[path];
|
|
2193
|
+
} else {
|
|
2194
|
+
logger8.warn(`\u65E0\u6CD5\u4ECE\u8DEF\u5F84\u89E3\u6790\u63D2\u4EF6 ID: ${path}\uFF0C\u8BF7\u68C0\u67E5\u662F\u5426\u7B26\u5408\u547D\u540D\u7EA6\u5B9A\u3002`);
|
|
2195
|
+
}
|
|
2196
|
+
} catch (e) {
|
|
2197
|
+
logger8.error(`\u89E3\u6790\u63D2\u4EF6\u8DEF\u5F84\u5931\u8D25: ${path}`, e);
|
|
2198
|
+
}
|
|
2199
|
+
}
|
|
2200
|
+
return registry;
|
|
2201
|
+
};
|
|
2202
|
+
|
|
2203
|
+
// src/domain/models.ts
|
|
2204
|
+
var SUCCESS_CODE = "000000";
|
|
2205
|
+
|
|
2206
|
+
// src/plugin-context.tsx
|
|
2207
|
+
var import_react5 = require("react");
|
|
2208
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
2209
|
+
var PluginContext = (0, import_react5.createContext)(null);
|
|
2210
|
+
var PluginProvider = ({ manager, children }) => {
|
|
2211
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PluginContext.Provider, { value: manager, children });
|
|
2212
|
+
};
|
|
2213
|
+
var usePluginManager = () => {
|
|
2214
|
+
const context = (0, import_react5.useContext)(PluginContext);
|
|
2215
|
+
if (!context) {
|
|
2216
|
+
throw new Error("usePluginManager must be used within a PluginProvider");
|
|
2217
|
+
}
|
|
2218
|
+
return context;
|
|
2219
|
+
};
|
|
2220
|
+
|
|
2221
|
+
// src/api/adapters/axios-adapter.ts
|
|
2222
|
+
var import_axios = __toESM(require("axios"));
|
|
2223
|
+
var AxiosAdapter = class {
|
|
2224
|
+
client;
|
|
2225
|
+
constructor(baseURL = "/api", timeout = 1e4) {
|
|
2226
|
+
this.client = import_axios.default.create({
|
|
2227
|
+
baseURL,
|
|
2228
|
+
timeout
|
|
2229
|
+
});
|
|
2230
|
+
this.client.interceptors.response.use(
|
|
2231
|
+
(response) => response,
|
|
2232
|
+
(error) => Promise.reject(error)
|
|
2233
|
+
);
|
|
2234
|
+
}
|
|
2235
|
+
async request(config) {
|
|
2236
|
+
return this.client.request(config);
|
|
2237
|
+
}
|
|
2238
|
+
async stream(config, callbacks, _endpointConfig) {
|
|
2239
|
+
const { onMessage, onError, onFinish } = callbacks;
|
|
2240
|
+
try {
|
|
2241
|
+
const response = await this.client.request({
|
|
2242
|
+
...config,
|
|
2243
|
+
// 强制不缓存流式响应
|
|
2244
|
+
headers: {
|
|
2245
|
+
"Cache-Control": "no-cache",
|
|
2246
|
+
"Pragma": "no-cache",
|
|
2247
|
+
...config.headers
|
|
2248
|
+
},
|
|
2249
|
+
// 自定义适配器:在拦截器链中执行 fetch
|
|
2250
|
+
adapter: async (axiosConfig) => {
|
|
2251
|
+
const url = this.client.getUri(axiosConfig);
|
|
2252
|
+
const fetchResponse2 = await fetch(url, {
|
|
2253
|
+
method: axiosConfig.method?.toUpperCase(),
|
|
2254
|
+
headers: axiosConfig.headers,
|
|
2255
|
+
body: axiosConfig.data ? typeof axiosConfig.data === "string" ? axiosConfig.data : JSON.stringify(axiosConfig.data) : void 0,
|
|
2256
|
+
signal: axiosConfig.signal
|
|
2257
|
+
});
|
|
2258
|
+
return {
|
|
2259
|
+
data: fetchResponse2,
|
|
2260
|
+
// 将原始 Response 存入 data,以便在拦截器链后获取
|
|
2261
|
+
status: fetchResponse2.status,
|
|
2262
|
+
statusText: fetchResponse2.statusText,
|
|
2263
|
+
headers: fetchResponse2.headers,
|
|
2264
|
+
config: axiosConfig
|
|
2265
|
+
};
|
|
2266
|
+
}
|
|
2267
|
+
});
|
|
2268
|
+
const fetchResponse = response instanceof Response ? response : response.data;
|
|
2269
|
+
if (callbacks.onResponse) {
|
|
2270
|
+
const hijacked = await callbacks.onResponse(response);
|
|
2271
|
+
if (hijacked) return;
|
|
2272
|
+
}
|
|
2273
|
+
if (!(fetchResponse instanceof Response)) {
|
|
2274
|
+
throw new Error("\u6D41\u5F0F\u8BF7\u6C42\u5931\u8D25\uFF1A\u672A\u80FD\u83B7\u53D6\u5230\u539F\u59CB\u54CD\u5E94\u6D41\uFF0C\u8BF7\u68C0\u67E5 Axios \u54CD\u5E94\u62E6\u622A\u5668\u662F\u5426\u6B63\u786E\u5904\u7406\u4E86 Response \u5BF9\u8C61\u3002");
|
|
2275
|
+
}
|
|
2276
|
+
if (!fetchResponse.ok) {
|
|
2277
|
+
throw new Error(`HTTP error! status: ${fetchResponse.status}`);
|
|
2278
|
+
}
|
|
2279
|
+
if (!fetchResponse.body) {
|
|
2280
|
+
throw new Error("\u54CD\u5E94\u4F53\u4E3A\u7A7A");
|
|
2281
|
+
}
|
|
2282
|
+
const reader = fetchResponse.body.getReader();
|
|
2283
|
+
const decoder = new TextDecoder();
|
|
2284
|
+
let buffer = "";
|
|
2285
|
+
while (true) {
|
|
2286
|
+
const { done, value } = await reader.read();
|
|
2287
|
+
if (done) break;
|
|
2288
|
+
buffer += decoder.decode(value, { stream: true });
|
|
2289
|
+
const parts = buffer.split("\n\n");
|
|
2290
|
+
buffer = parts.pop() || "";
|
|
2291
|
+
for (const part of parts) {
|
|
2292
|
+
if (onMessage) {
|
|
2293
|
+
onMessage(part + "\n\n");
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2296
|
+
}
|
|
2297
|
+
if (buffer && onMessage) {
|
|
2298
|
+
onMessage(buffer);
|
|
2299
|
+
}
|
|
2300
|
+
if (onFinish) onFinish();
|
|
2301
|
+
} catch (error) {
|
|
2302
|
+
if (onError) onError(error);
|
|
2303
|
+
}
|
|
2304
|
+
}
|
|
2305
|
+
};
|
|
2306
|
+
|
|
2307
|
+
// src/api/engine.ts
|
|
2308
|
+
var logger9 = createLogger("ApiEngine");
|
|
2309
|
+
var ApiEngine = class {
|
|
2310
|
+
adapter;
|
|
2311
|
+
config = {};
|
|
2312
|
+
interceptors = [];
|
|
2313
|
+
constructor(adapter) {
|
|
2314
|
+
this.adapter = adapter || new AxiosAdapter();
|
|
2315
|
+
}
|
|
2316
|
+
/**
|
|
2317
|
+
* 注册拦截器
|
|
2318
|
+
*/
|
|
2319
|
+
registerInterceptor(interceptor) {
|
|
2320
|
+
this.interceptors.push(interceptor);
|
|
2321
|
+
}
|
|
2322
|
+
/**
|
|
2323
|
+
* 移除拦截器
|
|
2324
|
+
*/
|
|
2325
|
+
unregisterInterceptor(interceptor) {
|
|
2326
|
+
this.interceptors = this.interceptors.filter((i) => i !== interceptor);
|
|
2327
|
+
}
|
|
2328
|
+
/**
|
|
2329
|
+
* 切换请求适配器
|
|
2330
|
+
* @param adapter 新的适配器实例
|
|
2331
|
+
*/
|
|
2332
|
+
useAdapter(adapter) {
|
|
2333
|
+
this.adapter = adapter;
|
|
2334
|
+
}
|
|
2335
|
+
/**
|
|
2336
|
+
* 注册 API 配置
|
|
2337
|
+
* @param config 配置对象
|
|
2338
|
+
*/
|
|
2339
|
+
register(config) {
|
|
2340
|
+
logger9.info("\u6B63\u5728\u6CE8\u518C API \u914D\u7F6E:", Object.keys(config));
|
|
2341
|
+
this.config = { ...this.config, ...config };
|
|
2342
|
+
}
|
|
2343
|
+
/**
|
|
2344
|
+
* 获取接口配置
|
|
2345
|
+
*/
|
|
2346
|
+
getEndpoint(module2, action) {
|
|
2347
|
+
return this.config[module2]?.[action];
|
|
2348
|
+
}
|
|
2349
|
+
/**
|
|
2350
|
+
* 发起 API 请求
|
|
2351
|
+
* @param module 模块名
|
|
2352
|
+
* @param action 动作名
|
|
2353
|
+
* @param data 请求数据 (Body 或 Query)
|
|
2354
|
+
* @param options 请求选项
|
|
2355
|
+
*/
|
|
2356
|
+
async call(module2, action, data, options = {}) {
|
|
2357
|
+
const endpoint = this.getEndpoint(module2, action);
|
|
2358
|
+
if (!endpoint) {
|
|
2359
|
+
logger9.warn(`\u672A\u627E\u5230 API \u5B9A\u4E49: ${module2}.${action} (\u5F53\u524D\u5DF2\u6CE8\u518C\u6A21\u5757: ${Object.keys(this.config).join(", ")})`);
|
|
2360
|
+
return Promise.resolve(void 0);
|
|
2361
|
+
}
|
|
2362
|
+
const requestConfig = await this.prepareRequestConfig(endpoint, data, options);
|
|
2363
|
+
let response;
|
|
2364
|
+
try {
|
|
2365
|
+
response = await this.adapter.request(requestConfig, endpoint);
|
|
2366
|
+
} catch (error) {
|
|
2367
|
+
if (error.response) {
|
|
2368
|
+
response = error.response;
|
|
2369
|
+
} else {
|
|
2370
|
+
throw error;
|
|
2371
|
+
}
|
|
2372
|
+
}
|
|
2373
|
+
const hijacked = await this.applyResponseInterceptors(response, requestConfig);
|
|
2374
|
+
if (hijacked) {
|
|
2375
|
+
logger9.info("\u8BF7\u6C42\u88AB\u62E6\u622A\u5668\u52AB\u6301:", module2, action);
|
|
2376
|
+
return void 0;
|
|
2377
|
+
}
|
|
2378
|
+
this.checkHttpStatus(response);
|
|
2379
|
+
const responseData = this.extractResponseData(response);
|
|
2380
|
+
this.handleBusinessError(responseData, endpoint, module2, action);
|
|
2381
|
+
return responseData;
|
|
2382
|
+
}
|
|
2383
|
+
/**
|
|
2384
|
+
* 发起流式请求
|
|
2385
|
+
* @param module 模块名
|
|
2386
|
+
* @param action 动作名
|
|
2387
|
+
* @param data 请求数据
|
|
2388
|
+
* @param options 请求选项
|
|
2389
|
+
*/
|
|
2390
|
+
async stream(module2, action, data, options = {}) {
|
|
2391
|
+
const endpoint = this.getEndpoint(module2, action);
|
|
2392
|
+
if (!endpoint) {
|
|
2393
|
+
logger9.warn(`\u672A\u627E\u5230 API \u5B9A\u4E49: ${module2}.${action}\uFF0C\u8DF3\u8FC7\u6D41\u5F0F\u8BF7\u6C42\u3002`);
|
|
2394
|
+
return;
|
|
2395
|
+
}
|
|
2396
|
+
if (!this.adapter.stream) {
|
|
2397
|
+
logger9.warn("\u5F53\u524D API \u9002\u914D\u5668\u4E0D\u652F\u6301\u6D41\u5F0F\u4F20\u8F93\u3002");
|
|
2398
|
+
return;
|
|
2399
|
+
}
|
|
2400
|
+
const requestConfig = await this.prepareRequestConfig(endpoint, data, options);
|
|
2401
|
+
const callbacks = {
|
|
2402
|
+
onMessage: options.onMessage,
|
|
2403
|
+
onError: options.onError,
|
|
2404
|
+
onFinish: options.onFinish
|
|
2405
|
+
};
|
|
2406
|
+
try {
|
|
2407
|
+
await this.adapter.stream(
|
|
2408
|
+
requestConfig,
|
|
2409
|
+
{
|
|
2410
|
+
...callbacks,
|
|
2411
|
+
onResponse: async (response) => {
|
|
2412
|
+
const hijacked = await this.applyResponseInterceptors(response, requestConfig);
|
|
2413
|
+
if (hijacked) {
|
|
2414
|
+
logger9.info("\u6D41\u5F0F\u8BF7\u6C42\u88AB\u62E6\u622A\u5668\u52AB\u6301:", module2, action);
|
|
2415
|
+
if (requestConfig.signal instanceof AbortController) {
|
|
2416
|
+
requestConfig.signal.abort();
|
|
2417
|
+
}
|
|
2418
|
+
return true;
|
|
2419
|
+
}
|
|
2420
|
+
this.checkHttpStatus(response);
|
|
2421
|
+
return false;
|
|
2422
|
+
}
|
|
2423
|
+
},
|
|
2424
|
+
endpoint
|
|
2425
|
+
);
|
|
2426
|
+
} catch (error) {
|
|
2427
|
+
if (callbacks.onError) {
|
|
2428
|
+
callbacks.onError(error);
|
|
2429
|
+
} else {
|
|
2430
|
+
throw error;
|
|
2431
|
+
}
|
|
2432
|
+
}
|
|
2433
|
+
}
|
|
2434
|
+
/**
|
|
2435
|
+
* 准备请求配置,应用 URL 参数替换和请求拦截器
|
|
2436
|
+
*/
|
|
2437
|
+
async prepareRequestConfig(endpoint, data, options) {
|
|
2438
|
+
let url = endpoint.url;
|
|
2439
|
+
const pathParams = options.params || {};
|
|
2440
|
+
url = url.replace(/:([a-zA-Z0-9_]+)/g, (_, key) => {
|
|
2441
|
+
if (pathParams[key] !== void 0) return String(pathParams[key]);
|
|
2442
|
+
if (data && typeof data === "object" && data[key] !== void 0) return String(data[key]);
|
|
2443
|
+
return `:${key}`;
|
|
2444
|
+
});
|
|
2445
|
+
const method = endpoint.method;
|
|
2446
|
+
let requestConfig = {
|
|
2447
|
+
...options,
|
|
2448
|
+
url,
|
|
2449
|
+
method
|
|
2450
|
+
};
|
|
2451
|
+
if (method === "GET" || method === "DELETE") {
|
|
2452
|
+
requestConfig.params = data;
|
|
2453
|
+
} else {
|
|
2454
|
+
requestConfig.data = data;
|
|
2455
|
+
}
|
|
2456
|
+
return this.applyRequestInterceptors(requestConfig);
|
|
2457
|
+
}
|
|
2458
|
+
/**
|
|
2459
|
+
* 应用所有请求拦截器
|
|
2460
|
+
*/
|
|
2461
|
+
async applyRequestInterceptors(config) {
|
|
2462
|
+
let currentConfig = config;
|
|
2463
|
+
for (const interceptor of this.interceptors) {
|
|
2464
|
+
if (interceptor.interceptRequest) {
|
|
2465
|
+
currentConfig = await interceptor.interceptRequest(currentConfig);
|
|
2466
|
+
}
|
|
2467
|
+
}
|
|
2468
|
+
return currentConfig;
|
|
2469
|
+
}
|
|
2470
|
+
/**
|
|
2471
|
+
* 应用所有响应拦截器
|
|
2472
|
+
* @returns 是否被劫持
|
|
2473
|
+
*/
|
|
2474
|
+
async applyResponseInterceptors(response, config) {
|
|
2475
|
+
if (this.interceptors.length === 0) return false;
|
|
2476
|
+
const context = this.createInterceptorContext(response, config);
|
|
2477
|
+
for (const interceptor of this.interceptors) {
|
|
2478
|
+
if (interceptor.interceptResponse) {
|
|
2479
|
+
const hijacked = await interceptor.interceptResponse(context);
|
|
2480
|
+
if (hijacked) return true;
|
|
2481
|
+
}
|
|
2482
|
+
}
|
|
2483
|
+
return false;
|
|
2484
|
+
}
|
|
2485
|
+
/**
|
|
2486
|
+
* 检查 HTTP 状态码
|
|
2487
|
+
*/
|
|
2488
|
+
checkHttpStatus(response) {
|
|
2489
|
+
if (response && response.status && (response.status < 200 || response.status >= 300)) {
|
|
2490
|
+
const responseData = this.extractResponseData(response);
|
|
2491
|
+
if (!this.isBaseResponse(responseData)) {
|
|
2492
|
+
throw new Error(`Request failed with status ${response.status}`);
|
|
2493
|
+
}
|
|
2494
|
+
}
|
|
2495
|
+
}
|
|
2496
|
+
/**
|
|
2497
|
+
* 提取响应数据
|
|
2498
|
+
*/
|
|
2499
|
+
extractResponseData(response) {
|
|
2500
|
+
return this.isAxiosResponse(response) ? response.data : response;
|
|
2501
|
+
}
|
|
2502
|
+
/**
|
|
2503
|
+
* 处理业务错误
|
|
2504
|
+
*/
|
|
2505
|
+
handleBusinessError(responseData, endpoint, module2, action) {
|
|
2506
|
+
if (!this.isBaseResponse(responseData)) return;
|
|
2507
|
+
const res = responseData;
|
|
2508
|
+
const code = String(res.code);
|
|
2509
|
+
const isSuccess = code === SUCCESS_CODE || code === "200" || code === "0";
|
|
2510
|
+
if (!isSuccess) {
|
|
2511
|
+
const strategy = endpoint.errorStrategy || "reject";
|
|
2512
|
+
if (strategy === "reject") {
|
|
2513
|
+
logger9.error(`API \u8BF7\u6C42\u4E1A\u52A1\u9519\u8BEF (${module2}.${action}):`, res.message);
|
|
2514
|
+
throw new Error(res.message || `Request failed with code ${code}`);
|
|
2515
|
+
}
|
|
2516
|
+
}
|
|
2517
|
+
}
|
|
2518
|
+
/**
|
|
2519
|
+
* 判断是否为 BaseResponse
|
|
2520
|
+
*/
|
|
2521
|
+
isBaseResponse(data) {
|
|
2522
|
+
return data && typeof data === "object" && "code" in data && ("message" in data || "data" in data);
|
|
2523
|
+
}
|
|
2524
|
+
/**
|
|
2525
|
+
* 严格判断是否为 AxiosResponse
|
|
2526
|
+
*/
|
|
2527
|
+
isAxiosResponse(res) {
|
|
2528
|
+
return res && typeof res === "object" && "data" in res && "status" in res && "headers" in res;
|
|
2529
|
+
}
|
|
2530
|
+
/**
|
|
2531
|
+
* 创建拦截上下文
|
|
2532
|
+
*/
|
|
2533
|
+
createInterceptorContext(response, config) {
|
|
2534
|
+
if (response && typeof response === "object" && "status" in response && "headers" in response) {
|
|
2535
|
+
return {
|
|
2536
|
+
response,
|
|
2537
|
+
status: response.status,
|
|
2538
|
+
headers: response.headers,
|
|
2539
|
+
data: response.data,
|
|
2540
|
+
config
|
|
2541
|
+
};
|
|
2542
|
+
}
|
|
2543
|
+
return {
|
|
2544
|
+
response,
|
|
2545
|
+
status: 200,
|
|
2546
|
+
headers: {},
|
|
2547
|
+
data: response,
|
|
2548
|
+
config
|
|
2549
|
+
};
|
|
2550
|
+
}
|
|
2551
|
+
};
|
|
2552
|
+
var apiEngine = new ApiEngine();
|
|
2553
|
+
|
|
2554
|
+
// src/utils/url.ts
|
|
2555
|
+
function normalizeParams(strategy = "last") {
|
|
2556
|
+
if (typeof window === "undefined") return new URLSearchParams();
|
|
2557
|
+
const { search, hash } = window.location;
|
|
2558
|
+
const searchParams = new URLSearchParams(search);
|
|
2559
|
+
const hashParams = new URLSearchParams(hash.split("?")[1] || "");
|
|
2560
|
+
const entries = [];
|
|
2561
|
+
searchParams.forEach((v, k) => entries.push([k, v, "search"]));
|
|
2562
|
+
hashParams.forEach((v, k) => entries.push([k, v, "hash"]));
|
|
2563
|
+
const keeper = /* @__PURE__ */ new Map();
|
|
2564
|
+
if (strategy === "first") {
|
|
2565
|
+
entries.forEach(([k, v]) => {
|
|
2566
|
+
if (!keeper.has(k)) keeper.set(k, v);
|
|
2567
|
+
});
|
|
2568
|
+
} else if (strategy === "last") {
|
|
2569
|
+
entries.forEach(([k, v]) => keeper.set(k, v));
|
|
2570
|
+
} else if (strategy === "search") {
|
|
2571
|
+
entries.forEach(([k, v, src]) => {
|
|
2572
|
+
if (src === "search") keeper.set(k, v);
|
|
2573
|
+
});
|
|
2574
|
+
hashParams.forEach((v, k) => {
|
|
2575
|
+
if (!keeper.has(k)) keeper.set(k, v);
|
|
2576
|
+
});
|
|
2577
|
+
} else if (strategy === "hash") {
|
|
2578
|
+
entries.forEach(([k, v, src]) => {
|
|
2579
|
+
if (src === "hash") keeper.set(k, v);
|
|
2580
|
+
});
|
|
2581
|
+
searchParams.forEach((v, k) => {
|
|
2582
|
+
if (!keeper.has(k)) keeper.set(k, v);
|
|
2583
|
+
});
|
|
2584
|
+
}
|
|
2585
|
+
return new URLSearchParams(Array.from(keeper.entries()));
|
|
2586
|
+
}
|
|
2587
|
+
function cleanUrlParams(keysToRemove) {
|
|
2588
|
+
if (typeof window === "undefined") return "";
|
|
2589
|
+
const { pathname, search, hash } = window.location;
|
|
2590
|
+
const searchParams = new URLSearchParams(search);
|
|
2591
|
+
keysToRemove.forEach((key) => searchParams.delete(key));
|
|
2592
|
+
const newSearch = searchParams.toString();
|
|
2593
|
+
const hashParts = hash.split("?");
|
|
2594
|
+
const hashPath = hashParts[0];
|
|
2595
|
+
const hashQuery = hashParts[1] || "";
|
|
2596
|
+
const hashParams = new URLSearchParams(hashQuery);
|
|
2597
|
+
keysToRemove.forEach((key) => hashParams.delete(key));
|
|
2598
|
+
const newHashQuery = hashParams.toString();
|
|
2599
|
+
const newHash = hashPath + (newHashQuery ? "?" + newHashQuery : "");
|
|
2600
|
+
return pathname + (newSearch ? "?" + newSearch : "") + newHash;
|
|
2601
|
+
}
|
|
2602
|
+
|
|
2603
|
+
// src/api/utils.ts
|
|
2604
|
+
var mergeMockData = (def, mock) => {
|
|
2605
|
+
const merged = JSON.parse(JSON.stringify(def));
|
|
2606
|
+
Object.keys(mock).forEach((key) => {
|
|
2607
|
+
if (merged[key]) {
|
|
2608
|
+
merged[key] = {
|
|
2609
|
+
...merged[key],
|
|
2610
|
+
...mock[key]
|
|
2611
|
+
};
|
|
2612
|
+
}
|
|
2613
|
+
});
|
|
2614
|
+
return merged;
|
|
2615
|
+
};
|
|
2616
|
+
function isMockMode() {
|
|
2617
|
+
const envMock = typeof process !== "undefined" && process.env.VITE_USE_MOCK === "true" || typeof window !== "undefined" && window.VITE_USE_MOCK === "true";
|
|
2618
|
+
if (typeof window !== "undefined") {
|
|
2619
|
+
const params = normalizeParams();
|
|
2620
|
+
const mockParam = params.get("mock");
|
|
2621
|
+
if (mockParam === "true") return true;
|
|
2622
|
+
if (mockParam === "false") return false;
|
|
2623
|
+
}
|
|
2624
|
+
return envMock;
|
|
2625
|
+
}
|
|
2626
|
+
function resolveApiModules(definitionsMap, mocksMap = {}) {
|
|
2627
|
+
const config = {};
|
|
2628
|
+
const getNamespace = (path) => {
|
|
2629
|
+
const fileName = path.split("/").pop() || "";
|
|
2630
|
+
return fileName.replace(/\.mock\.(ts|js|tsx|jsx|json)$/, "").replace(/\.(ts|js|tsx|jsx|json)$/, "");
|
|
2631
|
+
};
|
|
2632
|
+
Object.entries(definitionsMap).forEach(([path, module2]) => {
|
|
2633
|
+
if (path.includes(".mock.")) return;
|
|
2634
|
+
const namespace = getNamespace(path);
|
|
2635
|
+
if (!namespace || !module2.default) return;
|
|
2636
|
+
let apiDef = module2.default;
|
|
2637
|
+
const mockEntry = Object.entries(mocksMap).find(([mockPath]) => {
|
|
2638
|
+
return getNamespace(mockPath) === namespace && mockPath.includes(".mock.");
|
|
2639
|
+
});
|
|
2640
|
+
if (mockEntry) {
|
|
2641
|
+
const mockModule = mockEntry[1];
|
|
2642
|
+
const mockData = mockModule.default || mockModule;
|
|
2643
|
+
if (mockData) {
|
|
2644
|
+
apiDef = mergeMockData(apiDef, mockData);
|
|
2645
|
+
}
|
|
2646
|
+
}
|
|
2647
|
+
config[namespace] = apiDef;
|
|
2648
|
+
});
|
|
2649
|
+
return config;
|
|
2650
|
+
}
|
|
2651
|
+
|
|
2652
|
+
// src/utils/date.ts
|
|
2653
|
+
var import_dayjs = __toESM(require("dayjs"));
|
|
2654
|
+
var import_relativeTime = __toESM(require("dayjs/plugin/relativeTime"));
|
|
2655
|
+
var import_zh_cn = require("dayjs/locale/zh-cn");
|
|
2656
|
+
import_dayjs.default.extend(import_relativeTime.default);
|
|
2657
|
+
import_dayjs.default.locale("zh-cn");
|
|
2658
|
+
var dateUtils = {
|
|
2659
|
+
/**
|
|
2660
|
+
* 格式化日期为 YYYY-MM-DD
|
|
2661
|
+
*/
|
|
2662
|
+
formatDate(date) {
|
|
2663
|
+
return (0, import_dayjs.default)(date).format("YYYY-MM-DD");
|
|
2664
|
+
},
|
|
2665
|
+
/**
|
|
2666
|
+
* 格式化时间为 HH:mm:ss
|
|
2667
|
+
*/
|
|
2668
|
+
formatTime(date) {
|
|
2669
|
+
return (0, import_dayjs.default)(date).format("HH:mm:ss");
|
|
2670
|
+
},
|
|
2671
|
+
/**
|
|
2672
|
+
* 格式化日期时间为 YYYY-MM-DD HH:mm:ss
|
|
2673
|
+
*/
|
|
2674
|
+
formatDateTime(date) {
|
|
2675
|
+
return (0, import_dayjs.default)(date).format("YYYY-MM-DD HH:mm:ss");
|
|
2676
|
+
},
|
|
2677
|
+
/**
|
|
2678
|
+
* 获取当前时间戳(毫秒)
|
|
2679
|
+
*/
|
|
2680
|
+
now() {
|
|
2681
|
+
return (0, import_dayjs.default)().valueOf();
|
|
2682
|
+
},
|
|
2683
|
+
/**
|
|
2684
|
+
* 获取相对时间(例如:几分钟前)
|
|
2685
|
+
*/
|
|
2686
|
+
fromNow(date) {
|
|
2687
|
+
return (0, import_dayjs.default)(date).fromNow();
|
|
2688
|
+
},
|
|
2689
|
+
/**
|
|
2690
|
+
* 原始 dayjs 对象,用于更复杂的场景
|
|
2691
|
+
*/
|
|
2692
|
+
dayjs: import_dayjs.default
|
|
2693
|
+
};
|
|
2694
|
+
|
|
2695
|
+
// src/utils/index.ts
|
|
2696
|
+
var version = "1.0.0";
|
|
2697
|
+
|
|
2698
|
+
// src/hooks/use-storage-state.ts
|
|
2699
|
+
var import_react6 = require("react");
|
|
2700
|
+
function useStorageState(pluginId, key, options = {}) {
|
|
2701
|
+
const { defaultValue, scope = "plugin" } = options;
|
|
2702
|
+
const storageManager = pluginManager.getStorageManager();
|
|
2703
|
+
const getStorage = (0, import_react6.useCallback)(() => {
|
|
2704
|
+
const contextStorage = storageManager.getContextStorage(pluginId);
|
|
2705
|
+
return scope === "shared" ? contextStorage.shared : contextStorage;
|
|
2706
|
+
}, [pluginId, scope, storageManager]);
|
|
2707
|
+
const [state, setState] = (0, import_react6.useState)(() => {
|
|
2708
|
+
try {
|
|
2709
|
+
if (typeof window === "undefined") return defaultValue;
|
|
2710
|
+
const storage = getStorage();
|
|
2711
|
+
const val = storage.get(key);
|
|
2712
|
+
return val !== null ? val : defaultValue;
|
|
2713
|
+
} catch (e) {
|
|
2714
|
+
console.warn(`[useStorageState] \u8BFB\u53D6 Key "${key}" \u5931\u8D25`, e);
|
|
2715
|
+
return defaultValue;
|
|
2716
|
+
}
|
|
2717
|
+
});
|
|
2718
|
+
const setValue = (0, import_react6.useCallback)((value) => {
|
|
2719
|
+
try {
|
|
2720
|
+
const valueToStore = value instanceof Function ? value(state) : value;
|
|
2721
|
+
setState(valueToStore);
|
|
2722
|
+
const storage = getStorage();
|
|
2723
|
+
storage.set(key, valueToStore);
|
|
2724
|
+
} catch (error) {
|
|
2725
|
+
console.warn(`[useStorageState] \u8BBE\u7F6E Key "${key}" \u5931\u8D25:`, error);
|
|
2726
|
+
}
|
|
2727
|
+
}, [key, state, getStorage]);
|
|
2728
|
+
return [state, setValue];
|
|
2729
|
+
}
|
|
2730
|
+
|
|
2731
|
+
// src/hooks/use-plugin-loader.ts
|
|
2732
|
+
var import_react7 = require("react");
|
|
2733
|
+
var logger10 = createLogger("PluginLoader");
|
|
2734
|
+
var usePluginLoader = (options) => {
|
|
2735
|
+
const [pluginsLoaded, setPluginsLoaded] = (0, import_react7.useState)(false);
|
|
2736
|
+
const [pluginVersion, setPluginVersion] = (0, import_react7.useState)(0);
|
|
2737
|
+
const loadingRef = (0, import_react7.useRef)(false);
|
|
2738
|
+
(0, import_react7.useEffect)(() => {
|
|
2739
|
+
const unsubscribe = pluginManager.subscribe(() => {
|
|
2740
|
+
logger10.debug("Plugin state changed, refreshing UI...");
|
|
2741
|
+
setPluginVersion((v) => v + 1);
|
|
2742
|
+
});
|
|
2743
|
+
const load = async () => {
|
|
2744
|
+
if (loadingRef.current || pluginsLoaded) return;
|
|
2745
|
+
loadingRef.current = true;
|
|
2746
|
+
try {
|
|
2747
|
+
const {
|
|
2748
|
+
discoveryRules = [],
|
|
2749
|
+
modules = {},
|
|
2750
|
+
registry: manualRegistry = {},
|
|
2751
|
+
pluginConfigs,
|
|
2752
|
+
sharedContext = {},
|
|
2753
|
+
baseUrl = window.location.origin
|
|
2754
|
+
} = options;
|
|
2755
|
+
logger10.info("Starting to load plugins...");
|
|
2756
|
+
const discoveredRegistry = Object.keys(modules).length > 0 ? resolvePluginRegistry({
|
|
2757
|
+
modules,
|
|
2758
|
+
rules: discoveryRules.length > 0 ? discoveryRules : void 0
|
|
2759
|
+
}) : {};
|
|
2760
|
+
const finalRegistry = { ...discoveredRegistry, ...manualRegistry };
|
|
2761
|
+
if (options.systemConfig) {
|
|
2762
|
+
const { configManager: configManager2 } = await Promise.resolve().then(() => (init_config_manager(), config_manager_exports));
|
|
2763
|
+
configManager2.set("system", options.systemConfig);
|
|
2764
|
+
}
|
|
2765
|
+
await pluginManager.loadPlugins(pluginConfigs, finalRegistry);
|
|
2766
|
+
await pluginManager.initPlugins(sharedContext);
|
|
2767
|
+
setPluginsLoaded(true);
|
|
2768
|
+
logger10.info("Plugins loaded successfully");
|
|
2769
|
+
} catch (error) {
|
|
2770
|
+
logger10.error("Failed to load plugins:", error);
|
|
2771
|
+
} finally {
|
|
2772
|
+
loadingRef.current = false;
|
|
2773
|
+
}
|
|
2774
|
+
};
|
|
2775
|
+
load();
|
|
2776
|
+
return () => {
|
|
2777
|
+
unsubscribe();
|
|
2778
|
+
};
|
|
2779
|
+
}, []);
|
|
2780
|
+
return {
|
|
2781
|
+
pluginsLoaded,
|
|
2782
|
+
pluginVersion
|
|
2783
|
+
};
|
|
2784
|
+
};
|
|
2785
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
2786
|
+
0 && (module.exports = {
|
|
2787
|
+
ApiEngine,
|
|
2788
|
+
ApiProvider,
|
|
2789
|
+
AvatarSkeleton,
|
|
2790
|
+
AxiosAdapter,
|
|
2791
|
+
BasePlugin,
|
|
2792
|
+
BlockSkeleton,
|
|
2793
|
+
ConfigManager,
|
|
2794
|
+
DefaultEventBus,
|
|
2795
|
+
LocalStorageAdapter,
|
|
2796
|
+
LogLevel,
|
|
2797
|
+
Logger,
|
|
2798
|
+
PLUGIN_TYPES,
|
|
2799
|
+
PluginErrorBoundary,
|
|
2800
|
+
PluginManager,
|
|
2801
|
+
PluginProvider,
|
|
2802
|
+
PluginRuntime,
|
|
2803
|
+
PluginSandbox,
|
|
2804
|
+
PluginSlot,
|
|
2805
|
+
ProxySandbox,
|
|
2806
|
+
SUCCESS_CODE,
|
|
2807
|
+
ScopedStorageAdapter,
|
|
2808
|
+
ServiceRegistry,
|
|
2809
|
+
SidebarIconSkeleton,
|
|
2810
|
+
Slot,
|
|
2811
|
+
SlotSkeletons,
|
|
2812
|
+
StatusBarItemSkeleton,
|
|
2813
|
+
StorageManager,
|
|
2814
|
+
apiEngine,
|
|
2815
|
+
cleanUrlParams,
|
|
2816
|
+
configManager,
|
|
2817
|
+
createLogger,
|
|
2818
|
+
dateUtils,
|
|
2819
|
+
definePlugin,
|
|
2820
|
+
isMockMode,
|
|
2821
|
+
logger,
|
|
2822
|
+
normalizeParams,
|
|
2823
|
+
pluginManager,
|
|
2824
|
+
resolveApiModules,
|
|
2825
|
+
resolvePluginRegistry,
|
|
2826
|
+
serviceRegistry,
|
|
2827
|
+
useApi,
|
|
2828
|
+
usePluginLoader,
|
|
2829
|
+
usePluginManager,
|
|
2830
|
+
useStorageState,
|
|
2831
|
+
version
|
|
2832
|
+
});
|
|
2833
|
+
//# sourceMappingURL=index.js.map
|