yeow-api 0.5.0 → 0.6.0
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/package.json +1 -1
- package/src/core.ts +2 -2
- package/src/global.d.ts +10 -1
- package/src/service.ts +224 -66
- package/src/worker.ts +41 -4
package/package.json
CHANGED
package/src/core.ts
CHANGED
|
@@ -80,8 +80,8 @@ export { get as pdcGet, set as pdcSet, has as pdcHas, remove as pdcRemove, keys
|
|
|
80
80
|
export { add as addRecipe, remove as removeRecipe, getForItem as getRecipesForItem } from './recipe.js';
|
|
81
81
|
export { Material, getMaterials, getBlocks, getItems } from './material.js';
|
|
82
82
|
export type { MaterialInfo } from './material.js';
|
|
83
|
-
export {
|
|
84
|
-
export type {
|
|
83
|
+
export { Service, PluginService, NativeService, ServiceReply, registerService, registerNativeService, getService, hasService } from './service.js';
|
|
84
|
+
export type { ServiceKind, ServiceRequestOptions, ServiceResponse, NativeTerminateInfo, NativePlatforms } from './service.js';
|
|
85
85
|
export { log, Logger } from './log.js';
|
|
86
86
|
export type { Message } from './message.js';
|
|
87
87
|
export { getEnv } from './env.js';
|
package/src/global.d.ts
CHANGED
|
@@ -50,13 +50,22 @@ declare global {
|
|
|
50
50
|
|
|
51
51
|
interface TextDecoder {
|
|
52
52
|
readonly encoding: string;
|
|
53
|
-
decode(input?:
|
|
53
|
+
decode(input?: ArrayBuffer | ArrayBufferView): string;
|
|
54
54
|
}
|
|
55
55
|
const TextDecoder: {
|
|
56
56
|
new (encoding?: string): TextDecoder;
|
|
57
57
|
prototype: TextDecoder;
|
|
58
58
|
};
|
|
59
59
|
|
|
60
|
+
// ── performance(高精度单调时间)──
|
|
61
|
+
interface Performance {
|
|
62
|
+
/** 相对上下文起点的毫秒数(单调、亚毫秒精度)。 */
|
|
63
|
+
now(): number;
|
|
64
|
+
/** 上下文创建时的 epoch 毫秒。 */
|
|
65
|
+
readonly timeOrigin: number;
|
|
66
|
+
}
|
|
67
|
+
var performance: Performance;
|
|
68
|
+
|
|
60
69
|
function setTimeout(handler: (...args: any[]) => void, timeout?: number, ...args: any[]): string;
|
|
61
70
|
function clearTimeout(id: string): void;
|
|
62
71
|
function setInterval(handler: (...args: any[]) => void, timeout?: number, ...args: any[]): string;
|
package/src/service.ts
CHANGED
|
@@ -1,8 +1,32 @@
|
|
|
1
|
-
|
|
1
|
+
export type ServiceKind = 'plugin' | 'native';
|
|
2
2
|
|
|
3
|
-
export interface
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
export interface ServiceRequestOptions {
|
|
4
|
+
/** 请求头(app 级键值对;`content-type` 为典型项)。 */
|
|
5
|
+
headers?: Record<string, string>;
|
|
6
|
+
/** 便捷别名:等价于 `headers['content-type']`。 */
|
|
7
|
+
contentType?: string;
|
|
8
|
+
/** 请求体:JSON 值,或 `Uint8Array`(原始二进制,运行时解码后原样转发)。 */
|
|
9
|
+
body?: any;
|
|
10
|
+
/** 超时(毫秒);缺省用运行时配置(默认 30000)。 */
|
|
11
|
+
timeout?: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 服务响应(fetch 风格)。响应体始终以字节承载:`json()` / `text()` 在 JS 侧解码,
|
|
16
|
+
* `bytes()` / `arrayBuffer()` 直接取原始字节。`body` 为未来的可读流预留。
|
|
17
|
+
*/
|
|
18
|
+
export interface ServiceResponse {
|
|
19
|
+
readonly ok: boolean;
|
|
20
|
+
readonly status: number;
|
|
21
|
+
readonly contentType: string;
|
|
22
|
+
readonly headers: Record<string, string>;
|
|
23
|
+
/** 预留:未来的可读流(当前为 undefined)。 */
|
|
24
|
+
readonly body?: unknown;
|
|
25
|
+
base64(): Promise<string>;
|
|
26
|
+
bytes(): Promise<Uint8Array>;
|
|
27
|
+
arrayBuffer(): Promise<ArrayBuffer>;
|
|
28
|
+
text(): Promise<string>;
|
|
29
|
+
json(): Promise<any>;
|
|
6
30
|
}
|
|
7
31
|
|
|
8
32
|
export interface NativeTerminateInfo {
|
|
@@ -12,40 +36,87 @@ export interface NativeTerminateInfo {
|
|
|
12
36
|
output?: string;
|
|
13
37
|
}
|
|
14
38
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
onTerminate: (handler: (info: NativeTerminateInfo) => void) => void;
|
|
39
|
+
/** 服务回复:服务方 `onRequest` 返回它以携带响应头;返回普通值等价于 JSON body。 */
|
|
40
|
+
export class ServiceReply {
|
|
41
|
+
constructor(public readonly body: any, public readonly headers?: Record<string, string>) {}
|
|
19
42
|
}
|
|
20
43
|
|
|
21
|
-
|
|
44
|
+
type NativePlatform = string | { file: string };
|
|
45
|
+
export type NativePlatforms = Record<string, NativePlatform>;
|
|
46
|
+
|
|
47
|
+
// ── 内部实现 ──────────────────────────────────────────────────────
|
|
22
48
|
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
49
|
+
function _response(r: { headers?: Record<string, string>; contentType?: string; base64?: string; status?: number }): ServiceResponse {
|
|
50
|
+
const b64 = r.base64 ?? '';
|
|
51
|
+
const headers: Record<string, string> = {};
|
|
52
|
+
const raw = r.headers || {};
|
|
53
|
+
for (const k of Object.keys(raw)) headers[k.toLowerCase()] = raw[k];
|
|
54
|
+
const ct = r.contentType || headers['content-type'] || 'application/octet-stream';
|
|
55
|
+
headers['content-type'] = ct;
|
|
56
|
+
const decode = () => new TextDecoder().decode(Uint8Array.fromBase64(b64));
|
|
57
|
+
return {
|
|
58
|
+
ok: true,
|
|
59
|
+
status: r.status ?? 200,
|
|
60
|
+
contentType: ct,
|
|
61
|
+
headers,
|
|
62
|
+
base64: () => Promise.resolve(b64),
|
|
63
|
+
bytes: () => Promise.resolve(Uint8Array.fromBase64(b64)),
|
|
64
|
+
arrayBuffer: () => {
|
|
65
|
+
const u8 = Uint8Array.fromBase64(b64);
|
|
66
|
+
return Promise.resolve(u8.byteOffset === 0 && u8.byteLength === u8.buffer.byteLength ? u8.buffer : u8.slice().buffer);
|
|
67
|
+
},
|
|
68
|
+
text: () => Promise.resolve(decode()),
|
|
69
|
+
json: () => Promise.resolve().then(() => { const t = decode(); return t === '' ? null : JSON.parse(t); }),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function _request(serviceId: string, path: string, options: ServiceRequestOptions): Promise<ServiceResponse> {
|
|
74
|
+
return new Promise((resolve, reject) => {
|
|
75
|
+
const cbId = _registerCallback((result: any) => {
|
|
76
|
+
if (result?.err) { reject(new Error(result.err)); return; }
|
|
77
|
+
resolve(_response(result));
|
|
78
|
+
});
|
|
79
|
+
const headers: Record<string, string> = { ...(options.headers || {}) };
|
|
80
|
+
if (options.contentType && !headers['content-type']) headers['content-type'] = options.contentType;
|
|
81
|
+
const body = options.body;
|
|
82
|
+
if (body instanceof Uint8Array) {
|
|
83
|
+
if (!headers['content-type']) headers['content-type'] = 'application/octet-stream';
|
|
84
|
+
} else if (body !== undefined && body !== null) {
|
|
85
|
+
if (!headers['content-type']) headers['content-type'] = 'application/json';
|
|
86
|
+
} else if (!headers['content-type']) {
|
|
87
|
+
headers['content-type'] = 'application/json';
|
|
30
88
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
89
|
+
const p: Record<string, unknown> = { t: 'request', serviceId, path, requestId: cbId, headers, contentType: headers['content-type'] };
|
|
90
|
+
if (options.timeout !== undefined && options.timeout !== null) p.timeout = options.timeout;
|
|
91
|
+
if (body instanceof Uint8Array) {
|
|
92
|
+
p.body = body.toBase64();
|
|
93
|
+
p.bodyEncoding = 'base64';
|
|
94
|
+
} else if (body !== undefined && body !== null) {
|
|
95
|
+
p.body = body;
|
|
96
|
+
}
|
|
97
|
+
$send('service', p);
|
|
98
|
+
});
|
|
41
99
|
}
|
|
42
100
|
|
|
43
|
-
|
|
101
|
+
function _subscribe(serviceId: string, eventPath: string, handler: (body: any, eventPath: string) => void): () => void {
|
|
102
|
+
const cbId = _registerCallback((payload: any) => { handler(payload.body, payload.eventPath); }, { persistent: true });
|
|
103
|
+
$send('service', { t: 'subscribe', serviceId, eventPath, cb: cbId });
|
|
104
|
+
return () => {
|
|
105
|
+
$send('service', { t: 'unsubscribe', serviceId, eventPath });
|
|
106
|
+
_unregisterCallback(cbId);
|
|
107
|
+
};
|
|
108
|
+
}
|
|
44
109
|
|
|
45
|
-
|
|
46
|
-
|
|
110
|
+
function _unregister(serviceId: string, token?: string): Promise<void> {
|
|
111
|
+
return new Promise((resolve, reject) => {
|
|
112
|
+
const p: Record<string, unknown> = { t: 'unregister', serviceId };
|
|
113
|
+
if (token) p.token = token;
|
|
114
|
+
const r = $send('service', p) as any;
|
|
115
|
+
if (r?.err) reject(new Error(r.err)); else resolve();
|
|
116
|
+
});
|
|
117
|
+
}
|
|
47
118
|
|
|
48
|
-
function
|
|
119
|
+
function _awaitReady(serviceId: string): Promise<void> {
|
|
49
120
|
return new Promise((resolve, reject) => {
|
|
50
121
|
const cbId = _registerCallback((result: any) => {
|
|
51
122
|
if (result?.err) {
|
|
@@ -64,51 +135,138 @@ function _serviceReady(serviceId: string): Promise<void> {
|
|
|
64
135
|
});
|
|
65
136
|
}
|
|
66
137
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
138
|
+
function _info(serviceId: string): Promise<{ exists: boolean; kind: ServiceKind | null }> {
|
|
139
|
+
return new Promise((resolve, reject) => {
|
|
140
|
+
const r = $send('service', { t: 'info', serviceId }) as any;
|
|
141
|
+
if (r?.err) { reject(new Error(r.err)); return; }
|
|
142
|
+
resolve({ exists: !!r.exists, kind: (r.kind ?? null) as ServiceKind | null });
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ── Service 对象(OOP)────────────────────────────────────────────
|
|
147
|
+
|
|
148
|
+
/** 服务句柄基类(插件服务 / 原生服务通用能力)。 */
|
|
149
|
+
export abstract class Service {
|
|
150
|
+
abstract readonly kind: ServiceKind;
|
|
151
|
+
constructor(readonly id: string) {}
|
|
152
|
+
|
|
153
|
+
/** 请求服务(fetch 风格)。失败 / 超时 / 服务卸载时 Promise reject。 */
|
|
154
|
+
request(path: string, options: ServiceRequestOptions = {}): Promise<ServiceResponse> {
|
|
155
|
+
return _request(this.id, path, options);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** 订阅服务事件;返回取消函数。插件 unload / hot-reload 时运行时自动清理。 */
|
|
159
|
+
subscribe(eventPath: string, handler: (body: any, eventPath: string) => void): () => void {
|
|
160
|
+
return _subscribe(this.id, eventPath, handler);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** 卸载服务。Plugin Service 需属主 token,Native Service 需调用方为属主。 */
|
|
164
|
+
unregister(): Promise<void> {
|
|
165
|
+
return _unregister(this.id);
|
|
73
166
|
}
|
|
74
|
-
let terminateCb: string | null = null;
|
|
75
|
-
return {
|
|
76
|
-
serviceId: r.serviceId,
|
|
77
|
-
ready: () => _serviceReady(r.serviceId),
|
|
78
|
-
onTerminate(handler) {
|
|
79
|
-
if (terminateCb) _unregisterCallback(terminateCb);
|
|
80
|
-
terminateCb = _registerCallback((info: unknown) => handler(info as NativeTerminateInfo), { persistent: true });
|
|
81
|
-
$send('service', { t: 'registerNativeTerminate', serviceId: r.serviceId, cb: terminateCb });
|
|
82
|
-
},
|
|
83
|
-
};
|
|
84
167
|
}
|
|
85
168
|
|
|
86
|
-
|
|
169
|
+
/** 插件服务(JS 服务)。属主句柄(registerService)持有 token;消费者句柄(getService)无 token。 */
|
|
170
|
+
export class PluginService extends Service {
|
|
171
|
+
readonly kind = 'plugin' as const;
|
|
172
|
+
readonly token?: string;
|
|
173
|
+
constructor(id: string, token?: string) {
|
|
174
|
+
super(id);
|
|
175
|
+
this.token = token;
|
|
176
|
+
}
|
|
87
177
|
|
|
88
|
-
|
|
178
|
+
/** 发布事件(仅属主:需 token)。 */
|
|
179
|
+
publish(eventPath: string, body?: any): void {
|
|
180
|
+
if (!this.token) throw new Error('publish requires the owner token of plugin service ' + this.id);
|
|
181
|
+
$send('service', { t: 'publish', token: this.token, eventPath, body: body ?? {} });
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
override unregister(): Promise<void> {
|
|
185
|
+
if (!this.token) return Promise.reject(new Error('unregister requires the owner token of plugin service ' + this.id));
|
|
186
|
+
return _unregister(this.id, this.token);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** 原生服务(子进程)。 */
|
|
191
|
+
export class NativeService extends Service {
|
|
192
|
+
readonly kind = 'native' as const;
|
|
193
|
+
private _terminateCb: string | null = null;
|
|
194
|
+
constructor(id: string) { super(id); }
|
|
195
|
+
|
|
196
|
+
/** 等待原生服务就绪(进程 TCP 就绪消息已收到时 resolve)。 */
|
|
197
|
+
ready(): Promise<void> { return _awaitReady(this.id); }
|
|
198
|
+
|
|
199
|
+
/** 注册服务终止钩子(仅属主有效;重复调用替换旧回调)。 */
|
|
200
|
+
onTerminate(handler: (info: NativeTerminateInfo) => void): void {
|
|
201
|
+
if (this._terminateCb) _unregisterCallback(this._terminateCb);
|
|
202
|
+
this._terminateCb = _registerCallback((info: unknown) => handler(info as NativeTerminateInfo), { persistent: true });
|
|
203
|
+
$send('service', { t: 'registerNativeTerminate', serviceId: this.id, cb: this._terminateCb });
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// ── 注册 / 获取 ───────────────────────────────────────────────────
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* 注册 Plugin Service。成功返回 {@link PluginService}(属主句柄,含 token);
|
|
211
|
+
* 失败(权限 / 已存在)抛错——已存在时 `error.serviceId` 指向既有服务,应以其降级接入。
|
|
212
|
+
*/
|
|
213
|
+
export function registerService(refName: string, onRequest: (path: string, body: any) => any, isPublic = true): Promise<PluginService> {
|
|
89
214
|
return new Promise((resolve, reject) => {
|
|
90
|
-
const
|
|
91
|
-
if (
|
|
92
|
-
|
|
93
|
-
|
|
215
|
+
const svcCbId = _registerCallback((payload: any) => {
|
|
216
|
+
if (payload?._svc === 'request') {
|
|
217
|
+
const result = onRequest(payload.path, payload.body ?? null);
|
|
218
|
+
if (result instanceof ServiceReply) {
|
|
219
|
+
$send('service', { t: 'response', requestId: payload.requestId, headers: result.headers, body: result.body });
|
|
220
|
+
} else {
|
|
221
|
+
$send('service', { t: 'response', requestId: payload.requestId, body: result });
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}, { persistent: true });
|
|
225
|
+
const r = $send('service', { t: 'register', refName, onRequest: svcCbId, public: isPublic }) as any;
|
|
226
|
+
if (r?.err) {
|
|
227
|
+
_unregisterCallback(svcCbId);
|
|
228
|
+
const e: any = new Error(r.err);
|
|
229
|
+
if (r.serviceId) e.serviceId = r.serviceId;
|
|
230
|
+
reject(e);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
resolve(new PluginService(r.serviceId, r.token));
|
|
94
234
|
});
|
|
95
235
|
}
|
|
96
236
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
237
|
+
/** 注册 Native Service。成功返回 {@link NativeService};失败抛错。 */
|
|
238
|
+
export function registerNativeService(refName: string, platforms: NativePlatforms, isPublic = true): Promise<NativeService> {
|
|
239
|
+
return new Promise((resolve, reject) => {
|
|
240
|
+
const r = $send('service', { t: 'registerNative', refName, platforms, public: isPublic }) as any;
|
|
241
|
+
if (r?.err) {
|
|
242
|
+
const e: any = new Error(r.err);
|
|
243
|
+
if (r.serviceId) e.serviceId = r.serviceId;
|
|
244
|
+
reject(e);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
resolve(new NativeService(r.serviceId));
|
|
248
|
+
});
|
|
108
249
|
}
|
|
109
250
|
|
|
110
|
-
|
|
251
|
+
/** 按 id 获取服务句柄;不存在时抛错。返回的消费者句柄无 token(不能 publish / unregister 插件服务)。 */
|
|
252
|
+
export function getService(serviceId: string): Promise<Service> {
|
|
253
|
+
return _info(serviceId).then(i => {
|
|
254
|
+
if (!i.exists) {
|
|
255
|
+
const e: any = new Error('Service not found: ' + serviceId);
|
|
256
|
+
e.serviceId = serviceId;
|
|
257
|
+
throw e;
|
|
258
|
+
}
|
|
259
|
+
return i.kind === 'native' ? new NativeService(serviceId) : new PluginService(serviceId);
|
|
260
|
+
});
|
|
261
|
+
}
|
|
111
262
|
|
|
112
|
-
|
|
113
|
-
|
|
263
|
+
/**
|
|
264
|
+
* 检查服务是否存在。
|
|
265
|
+
*
|
|
266
|
+
* > **不要用它判断后再注册**:检查与注册**不是原子操作**,并发下会竞态——两个插件可能同时检查到
|
|
267
|
+
* > 不存在,随后一个注册成功、另一个被拒绝。注册必须用 `try-catch` 包裹,并在捕获到「已存在」时用
|
|
268
|
+
* > `error.serviceId` 降级接入既有服务。此方法仅用于展示 / 诊断。
|
|
269
|
+
*/
|
|
270
|
+
export function hasService(serviceId: string): Promise<boolean> {
|
|
271
|
+
return _info(serviceId).then(i => i.exists);
|
|
114
272
|
}
|
package/src/worker.ts
CHANGED
|
@@ -5,6 +5,19 @@ export interface WorkerOptions {
|
|
|
5
5
|
entry?: string;
|
|
6
6
|
/** 代码字符串;与 `entry` 互斥。 */
|
|
7
7
|
code?: string;
|
|
8
|
+
/**
|
|
9
|
+
* 权限覆盖(可选)。默认继承主插件全部权限。
|
|
10
|
+
* - `allow`:白名单——只有命中的节点允许(未声明时继承主插件权限)
|
|
11
|
+
* - `deny`:黑名单——优先级最高(先于 allow 与继承)
|
|
12
|
+
*
|
|
13
|
+
* 节点格式 `channel:op`(如 `fs:server.readFile`、`http:*`、`task:player.*`、`*`)。
|
|
14
|
+
* **不能提权**:主插件未声明的权限,`allow` 无效(仍按默认拒绝)。
|
|
15
|
+
* 例:`deny: ['*']` 只允许标准 ES 代码;`deny: ['fs:*', 'http:*']` 仅禁用这两类。
|
|
16
|
+
*/
|
|
17
|
+
permissions?: {
|
|
18
|
+
allow?: string[];
|
|
19
|
+
deny?: string[];
|
|
20
|
+
};
|
|
8
21
|
}
|
|
9
22
|
|
|
10
23
|
let _seq = 0;
|
|
@@ -35,9 +48,10 @@ function _sendWorkerAsync(t: string, p: Record<string, unknown>): Promise<void>
|
|
|
35
48
|
* Worker —— 虚拟插件(独立 QuickJS 上下文 + 线程)。
|
|
36
49
|
*
|
|
37
50
|
* - 事件/命令/服务以独立实体注册;调度器任务独立统计
|
|
38
|
-
* -
|
|
51
|
+
* - 共享主插件的**数据目录**;权限默认继承主插件,可按需用 `allow`/`deny` 收紧(不可提权)
|
|
39
52
|
* - 不能创建新的 Worker(嵌套被拒绝)
|
|
40
|
-
* -
|
|
53
|
+
* - `unload()` 卸载(物理销毁 JS 上下文,句柄保留——可重新 load);
|
|
54
|
+
* `destroy()` **彻底销毁**(移除注册,句柄作废,同名可重建)
|
|
41
55
|
* - 主插件卸载时连带卸载;/yeow 管理命令不覆盖 Worker;profiler 会统计(标记 created by 主插件)
|
|
42
56
|
* - Worker 的 JS 错误与主插件同样回传(dev 模式经 source-map 定位)
|
|
43
57
|
*/
|
|
@@ -46,15 +60,18 @@ export class Worker {
|
|
|
46
60
|
private readonly entry?: string;
|
|
47
61
|
private readonly code?: string;
|
|
48
62
|
private readonly key: string;
|
|
63
|
+
private readonly permissions?: { allow?: string[]; deny?: string[] };
|
|
49
64
|
private _loaded = false;
|
|
65
|
+
private _destroyed = false;
|
|
50
66
|
private _onMessage: ((msg: any) => void) | null = null;
|
|
51
67
|
/** 内部 workerId(主插件 JS 侧分配;跨主插件可重复)。 */
|
|
52
68
|
readonly id: string;
|
|
53
69
|
|
|
54
|
-
constructor(name: string, entry: string | undefined, code: string | undefined) {
|
|
70
|
+
constructor(name: string, entry: string | undefined, code: string | undefined, permissions?: { allow?: string[]; deny?: string[] }) {
|
|
55
71
|
this.name = name;
|
|
56
72
|
this.entry = entry;
|
|
57
73
|
this.code = code;
|
|
74
|
+
this.permissions = permissions;
|
|
58
75
|
this.id = 'worker_' + (++_seq);
|
|
59
76
|
this.key = (__plugin?.name || 'unknown') + ':' + name;
|
|
60
77
|
// 主插件侧 onMessage 回调(worker → main 时 Java 投递到这里)
|
|
@@ -71,6 +88,7 @@ export class Worker {
|
|
|
71
88
|
|
|
72
89
|
/** 启动 Worker:执行 init.js → worker-inject.js → Worker 代码 → INIT → LOAD(已加载为 no-op)。 */
|
|
73
90
|
load(): Promise<void> {
|
|
91
|
+
if (this._destroyed) return Promise.reject(new Error('worker has been destroyed'));
|
|
74
92
|
if (this._loaded) return Promise.resolve();
|
|
75
93
|
this._loaded = true;
|
|
76
94
|
return _sendWorkerAsync('load', { name: this.name });
|
|
@@ -78,22 +96,40 @@ export class Worker {
|
|
|
78
96
|
|
|
79
97
|
/** 卸载 Worker(物理销毁 JS 上下文并清理其事件/命令/服务/任务;句柄保留,可重新 load)。 */
|
|
80
98
|
unload(): Promise<void> {
|
|
99
|
+
if (this._destroyed) return Promise.reject(new Error('worker has been destroyed'));
|
|
81
100
|
this._loaded = false;
|
|
82
101
|
return _sendWorkerAsync('unload', { name: this.name });
|
|
83
102
|
}
|
|
84
103
|
|
|
85
104
|
/** 向 Worker 发送消息(其 onMessage 回调接收;未 load 时抛错)。 */
|
|
86
105
|
postMessage(msg: Record<string, unknown>): Promise<void> {
|
|
106
|
+
if (this._destroyed) return Promise.reject(new Error('worker has been destroyed'));
|
|
87
107
|
return _sendWorkerAsync('post', { name: this.name, msg });
|
|
88
108
|
}
|
|
89
109
|
|
|
90
110
|
/** 重载 Worker 代码(需已 load;旧上下文销毁、新代码重新加载)。 */
|
|
91
111
|
reload(): Promise<void> {
|
|
112
|
+
if (this._destroyed) return Promise.reject(new Error('worker has been destroyed'));
|
|
92
113
|
const p: Record<string, unknown> = { name: this.name };
|
|
93
114
|
if (this.entry) p.entry = this.entry;
|
|
94
115
|
else p.code = this.code;
|
|
95
116
|
return _sendWorkerAsync('reload', p);
|
|
96
117
|
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* **彻底销毁** Worker:卸载并移除注册(清理事件/命令/服务/任务),句柄作废——
|
|
121
|
+
* 之后 load/post/reload 均 reject;同名 Worker 可重新 `createWorker`。
|
|
122
|
+
*/
|
|
123
|
+
destroy(): Promise<void> {
|
|
124
|
+
if (this._destroyed) return Promise.resolve();
|
|
125
|
+
return _sendWorkerAsync('destroy', { name: this.name }).then(() => {
|
|
126
|
+
this._destroyed = true;
|
|
127
|
+
this._loaded = false;
|
|
128
|
+
const cb = _msgCbs[this.id];
|
|
129
|
+
if (cb) { _unregisterCallback(cb); delete _msgCbs[this.id]; }
|
|
130
|
+
delete _created[this.key];
|
|
131
|
+
});
|
|
132
|
+
}
|
|
97
133
|
}
|
|
98
134
|
|
|
99
135
|
/**
|
|
@@ -122,11 +158,12 @@ export function createWorker(options: WorkerOptions): Worker {
|
|
|
122
158
|
throw new Error('createWorker: duplicate worker name "' + name + '" in plugin ' + (__plugin?.name || 'unknown'));
|
|
123
159
|
}
|
|
124
160
|
_created[key] = true;
|
|
125
|
-
const w = new Worker(name, options.entry, options.code);
|
|
161
|
+
const w = new Worker(name, options.entry, options.code, options.permissions);
|
|
126
162
|
// 注册到运行时注册表(同步;重复/非法名抛错)
|
|
127
163
|
const p: Record<string, unknown> = { name, msgCb: _msgCbs[w.id] };
|
|
128
164
|
if (options.entry) p.entry = options.entry;
|
|
129
165
|
else p.code = options.code;
|
|
166
|
+
if (options.permissions) p.permissions = options.permissions;
|
|
130
167
|
_sendWorker({ t: 'create', p });
|
|
131
168
|
return w;
|
|
132
169
|
}
|