@syllm/brickly-sdk 0.1.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/README.md +402 -0
- package/dist/api.d.ts +412 -0
- package/dist/api.js +995 -0
- package/dist/errors.d.ts +11 -0
- package/dist/errors.js +68 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +16 -0
- package/dist/protocol.d.ts +530 -0
- package/dist/protocol.js +11 -0
- package/dist/runtime.d.ts +64 -0
- package/dist/runtime.js +177 -0
- package/dist/typegen-cli.d.ts +2 -0
- package/dist/typegen-cli.js +68 -0
- package/dist/typegen.d.ts +24 -0
- package/dist/typegen.js +470 -0
- package/package.json +30 -0
package/dist/api.js
ADDED
|
@@ -0,0 +1,995 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BricklyRuntime = exports.WindowHandle = exports.BrickSession = void 0;
|
|
4
|
+
const node_events_1 = require("node:events");
|
|
5
|
+
const node_async_hooks_1 = require("node:async_hooks");
|
|
6
|
+
const errors_1 = require("./errors");
|
|
7
|
+
const runtime_1 = require("./runtime");
|
|
8
|
+
const protocol_1 = require("./protocol");
|
|
9
|
+
const commandScope = new node_async_hooks_1.AsyncLocalStorage();
|
|
10
|
+
function currentCommandScope() {
|
|
11
|
+
return commandScope.getStore();
|
|
12
|
+
}
|
|
13
|
+
class BrickSession {
|
|
14
|
+
transport;
|
|
15
|
+
id;
|
|
16
|
+
brickId;
|
|
17
|
+
profileId;
|
|
18
|
+
constructor(transport, id, brickId, profileId) {
|
|
19
|
+
this.transport = transport;
|
|
20
|
+
this.id = id;
|
|
21
|
+
this.brickId = brickId;
|
|
22
|
+
this.profileId = profileId;
|
|
23
|
+
}
|
|
24
|
+
invoke(commandId, input = null) {
|
|
25
|
+
const parentRequestId = currentCommandScope()?.requestId;
|
|
26
|
+
if (!parentRequestId) {
|
|
27
|
+
return Promise.reject(new errors_1.BppError('PARENT_INVOCATION_REQUIRED', 'session.invoke() must run inside command handler'));
|
|
28
|
+
}
|
|
29
|
+
return this.transport.hostCall({
|
|
30
|
+
type: 'host.session.invoke',
|
|
31
|
+
sessionId: this.id,
|
|
32
|
+
commandId,
|
|
33
|
+
input,
|
|
34
|
+
parentRequestId
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
async close() {
|
|
38
|
+
await this.transport.hostCall({
|
|
39
|
+
type: 'host.session.close',
|
|
40
|
+
sessionId: this.id
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
exports.BrickSession = BrickSession;
|
|
45
|
+
function pointPayload(xOrPayload, y) {
|
|
46
|
+
return typeof xOrPayload === 'number' ? { x: xOrPayload, y: y } : xOrPayload;
|
|
47
|
+
}
|
|
48
|
+
/** 子窗口句柄。close() 调 host.ui.closeWindow,其余方法走 host.ui.callWindow。 */
|
|
49
|
+
class WindowHandle extends node_events_1.EventEmitter {
|
|
50
|
+
id;
|
|
51
|
+
transport;
|
|
52
|
+
closed = false;
|
|
53
|
+
constructor(transport, windowId) {
|
|
54
|
+
super();
|
|
55
|
+
this.transport = transport;
|
|
56
|
+
this.id = windowId;
|
|
57
|
+
}
|
|
58
|
+
/** 通用:调用窗口白名单方法。 */
|
|
59
|
+
call(method, args = []) {
|
|
60
|
+
if (method === 'webContents.send') {
|
|
61
|
+
const [channel, ...sendArgs] = args;
|
|
62
|
+
if (typeof channel !== 'string') {
|
|
63
|
+
return Promise.reject(new errors_1.BppError('INVALID_INPUT', 'webContents.send requires channel'));
|
|
64
|
+
}
|
|
65
|
+
return this.sendToWebContents(channel, sendArgs);
|
|
66
|
+
}
|
|
67
|
+
if (this.closed && method !== 'isDestroyed') {
|
|
68
|
+
return Promise.reject(new errors_1.BppError('INVALID_INPUT', `Window ${this.id} already closed`));
|
|
69
|
+
}
|
|
70
|
+
return this.transport.hostCall({
|
|
71
|
+
type: 'host.ui.callWindow',
|
|
72
|
+
windowId: this.id,
|
|
73
|
+
method,
|
|
74
|
+
args
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
sendToWebContents(channel, args) {
|
|
78
|
+
if (this.closed) {
|
|
79
|
+
return Promise.reject(new errors_1.BppError('INVALID_INPUT', `Window ${this.id} already closed`));
|
|
80
|
+
}
|
|
81
|
+
const parentRequestId = currentCommandScope()?.requestId ?? explicitPayloadRequestId(args);
|
|
82
|
+
if (!parentRequestId) {
|
|
83
|
+
return Promise.reject(new errors_1.BppError('PARENT_INVOCATION_REQUIRED', 'webContents.send() must run inside command handler or include payload.requestId'));
|
|
84
|
+
}
|
|
85
|
+
return this.transport.hostCall({
|
|
86
|
+
type: 'host.ui.callWindow',
|
|
87
|
+
windowId: this.id,
|
|
88
|
+
method: 'webContents.send',
|
|
89
|
+
args: [channel, ...args],
|
|
90
|
+
parentRequestId
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
/** 订阅该窗口的网络检查事件。需在 createBrowserWindow options.network 中开启。 */
|
|
94
|
+
onNetwork(handler) {
|
|
95
|
+
this.on('network', handler);
|
|
96
|
+
return this;
|
|
97
|
+
}
|
|
98
|
+
// —— 便捷包装:完整覆盖 BrickWindowMethod 白名单(41 个) ——
|
|
99
|
+
// 几何 / 位置
|
|
100
|
+
setBounds(bounds) {
|
|
101
|
+
return this.call('setBounds', [bounds]);
|
|
102
|
+
}
|
|
103
|
+
getBounds() {
|
|
104
|
+
return this.call('getBounds');
|
|
105
|
+
}
|
|
106
|
+
setPosition(x, y) {
|
|
107
|
+
return this.call('setPosition', [x, y]);
|
|
108
|
+
}
|
|
109
|
+
getPosition() {
|
|
110
|
+
return this.call('getPosition');
|
|
111
|
+
}
|
|
112
|
+
setSize(width, height) {
|
|
113
|
+
return this.call('setSize', [width, height]);
|
|
114
|
+
}
|
|
115
|
+
getSize() {
|
|
116
|
+
return this.call('getSize');
|
|
117
|
+
}
|
|
118
|
+
// 视觉属性
|
|
119
|
+
setOpacity(opacity) {
|
|
120
|
+
return this.call('setOpacity', [opacity]);
|
|
121
|
+
}
|
|
122
|
+
getOpacity() {
|
|
123
|
+
return this.call('getOpacity');
|
|
124
|
+
}
|
|
125
|
+
setBackgroundColor(color) {
|
|
126
|
+
return this.call('setBackgroundColor', [color]);
|
|
127
|
+
}
|
|
128
|
+
setHasShadow(hasShadow) {
|
|
129
|
+
return this.call('setHasShadow', [hasShadow]);
|
|
130
|
+
}
|
|
131
|
+
setTitle(title) {
|
|
132
|
+
return this.call('setTitle', [title]);
|
|
133
|
+
}
|
|
134
|
+
getTitle() {
|
|
135
|
+
return this.call('getTitle');
|
|
136
|
+
}
|
|
137
|
+
// 层叠 / 鼠标
|
|
138
|
+
setAlwaysOnTop(flag, level) {
|
|
139
|
+
return this.call('setAlwaysOnTop', level === undefined ? [flag] : [flag, level]);
|
|
140
|
+
}
|
|
141
|
+
isAlwaysOnTop() {
|
|
142
|
+
return this.call('isAlwaysOnTop');
|
|
143
|
+
}
|
|
144
|
+
setIgnoreMouseEvents(ignore, opts) {
|
|
145
|
+
return this.call('setIgnoreMouseEvents', opts === undefined ? [ignore] : [ignore, opts]);
|
|
146
|
+
}
|
|
147
|
+
setSkipTaskbar(skip) {
|
|
148
|
+
return this.call('setSkipTaskbar', [skip]);
|
|
149
|
+
}
|
|
150
|
+
setVisibleOnAllWorkspaces(visible, opts) {
|
|
151
|
+
return this.call('setVisibleOnAllWorkspaces', opts === undefined ? [visible] : [visible, opts]);
|
|
152
|
+
}
|
|
153
|
+
// 可交互性开关
|
|
154
|
+
setResizable(resizable) {
|
|
155
|
+
return this.call('setResizable', [resizable]);
|
|
156
|
+
}
|
|
157
|
+
setMovable(movable) {
|
|
158
|
+
return this.call('setMovable', [movable]);
|
|
159
|
+
}
|
|
160
|
+
setFocusable(focusable) {
|
|
161
|
+
return this.call('setFocusable', [focusable]);
|
|
162
|
+
}
|
|
163
|
+
// 状态切换
|
|
164
|
+
minimize() {
|
|
165
|
+
return this.call('minimize');
|
|
166
|
+
}
|
|
167
|
+
maximize() {
|
|
168
|
+
return this.call('maximize');
|
|
169
|
+
}
|
|
170
|
+
unmaximize() {
|
|
171
|
+
return this.call('unmaximize');
|
|
172
|
+
}
|
|
173
|
+
restore() {
|
|
174
|
+
return this.call('restore');
|
|
175
|
+
}
|
|
176
|
+
hide() {
|
|
177
|
+
return this.call('hide');
|
|
178
|
+
}
|
|
179
|
+
show() {
|
|
180
|
+
return this.call('show');
|
|
181
|
+
}
|
|
182
|
+
showInactive() {
|
|
183
|
+
return this.call('showInactive');
|
|
184
|
+
}
|
|
185
|
+
focus() {
|
|
186
|
+
return this.call('focus');
|
|
187
|
+
}
|
|
188
|
+
blur() {
|
|
189
|
+
return this.call('blur');
|
|
190
|
+
}
|
|
191
|
+
// 状态查询
|
|
192
|
+
isDestroyed() {
|
|
193
|
+
return this.call('isDestroyed');
|
|
194
|
+
}
|
|
195
|
+
isVisible() {
|
|
196
|
+
return this.call('isVisible');
|
|
197
|
+
}
|
|
198
|
+
isFocused() {
|
|
199
|
+
return this.call('isFocused');
|
|
200
|
+
}
|
|
201
|
+
isMinimized() {
|
|
202
|
+
return this.call('isMinimized');
|
|
203
|
+
}
|
|
204
|
+
isMaximized() {
|
|
205
|
+
return this.call('isMaximized');
|
|
206
|
+
}
|
|
207
|
+
// 内容加载
|
|
208
|
+
loadURL(url, options) {
|
|
209
|
+
return this.call('loadURL', options === undefined ? [url] : [url, options]);
|
|
210
|
+
}
|
|
211
|
+
loadFile(filePath, options) {
|
|
212
|
+
return this.call('loadFile', options === undefined ? [filePath] : [filePath, options]);
|
|
213
|
+
}
|
|
214
|
+
reload() {
|
|
215
|
+
return this.call('reload');
|
|
216
|
+
}
|
|
217
|
+
// —— v0.2 扩展窗口方法 ——
|
|
218
|
+
// 内容区域几何(区分窗口外框 vs 网页内容)
|
|
219
|
+
setContentBounds(bounds) {
|
|
220
|
+
return this.call('setContentBounds', [bounds]);
|
|
221
|
+
}
|
|
222
|
+
getContentBounds() {
|
|
223
|
+
return this.call('getContentBounds');
|
|
224
|
+
}
|
|
225
|
+
setContentSize(width, height) {
|
|
226
|
+
return this.call('setContentSize', [width, height]);
|
|
227
|
+
}
|
|
228
|
+
getContentSize() {
|
|
229
|
+
return this.call('getContentSize');
|
|
230
|
+
}
|
|
231
|
+
setMinimumSize(width, height) {
|
|
232
|
+
return this.call('setMinimumSize', [width, height]);
|
|
233
|
+
}
|
|
234
|
+
getMinimumSize() {
|
|
235
|
+
return this.call('getMinimumSize');
|
|
236
|
+
}
|
|
237
|
+
setMaximumSize(width, height) {
|
|
238
|
+
return this.call('setMaximumSize', [width, height]);
|
|
239
|
+
}
|
|
240
|
+
getMaximumSize() {
|
|
241
|
+
return this.call('getMaximumSize');
|
|
242
|
+
}
|
|
243
|
+
setAspectRatio(aspectRatio, extraSize) {
|
|
244
|
+
return this.call('setAspectRatio', extraSize === undefined ? [aspectRatio] : [aspectRatio, extraSize]);
|
|
245
|
+
}
|
|
246
|
+
/** 获取窗口在非最大化/全屏/最小化时的"普通"位置和尺寸。 */
|
|
247
|
+
getNormalBounds() {
|
|
248
|
+
return this.call('getNormalBounds');
|
|
249
|
+
}
|
|
250
|
+
// 居中 / 层叠
|
|
251
|
+
center() {
|
|
252
|
+
return this.call('center');
|
|
253
|
+
}
|
|
254
|
+
moveTop() {
|
|
255
|
+
return this.call('moveTop');
|
|
256
|
+
}
|
|
257
|
+
/** mediaSourceId 由 Electron getMediaSourceId 提供;通常在同 Brick 多窗口间使用。 */
|
|
258
|
+
moveAbove(mediaSourceId) {
|
|
259
|
+
return this.call('moveAbove', [mediaSourceId]);
|
|
260
|
+
}
|
|
261
|
+
// 全屏 / 状态查询
|
|
262
|
+
setFullScreen(flag) {
|
|
263
|
+
return this.call('setFullScreen', [flag]);
|
|
264
|
+
}
|
|
265
|
+
isFullScreen() {
|
|
266
|
+
return this.call('isFullScreen');
|
|
267
|
+
}
|
|
268
|
+
isNormal() {
|
|
269
|
+
return this.call('isNormal');
|
|
270
|
+
}
|
|
271
|
+
isModal() {
|
|
272
|
+
return this.call('isModal');
|
|
273
|
+
}
|
|
274
|
+
/** 强制销毁窗口(不触发 close 事件),等价于 BrowserWindow.destroy()。慎用。 */
|
|
275
|
+
destroy() {
|
|
276
|
+
this.closed = true;
|
|
277
|
+
return this.call('destroy');
|
|
278
|
+
}
|
|
279
|
+
// 能力查询(与 setter 配对)
|
|
280
|
+
isResizable() {
|
|
281
|
+
return this.call('isResizable');
|
|
282
|
+
}
|
|
283
|
+
isMovable() {
|
|
284
|
+
return this.call('isMovable');
|
|
285
|
+
}
|
|
286
|
+
isFocusable() {
|
|
287
|
+
return this.call('isFocusable');
|
|
288
|
+
}
|
|
289
|
+
setMinimizable(flag) {
|
|
290
|
+
return this.call('setMinimizable', [flag]);
|
|
291
|
+
}
|
|
292
|
+
isMinimizable() {
|
|
293
|
+
return this.call('isMinimizable');
|
|
294
|
+
}
|
|
295
|
+
setMaximizable(flag) {
|
|
296
|
+
return this.call('setMaximizable', [flag]);
|
|
297
|
+
}
|
|
298
|
+
isMaximizable() {
|
|
299
|
+
return this.call('isMaximizable');
|
|
300
|
+
}
|
|
301
|
+
setClosable(flag) {
|
|
302
|
+
return this.call('setClosable', [flag]);
|
|
303
|
+
}
|
|
304
|
+
isClosable() {
|
|
305
|
+
return this.call('isClosable');
|
|
306
|
+
}
|
|
307
|
+
setFullScreenable(flag) {
|
|
308
|
+
return this.call('setFullScreenable', [flag]);
|
|
309
|
+
}
|
|
310
|
+
isFullScreenable() {
|
|
311
|
+
return this.call('isFullScreenable');
|
|
312
|
+
}
|
|
313
|
+
setEnabled(enabled) {
|
|
314
|
+
return this.call('setEnabled', [enabled]);
|
|
315
|
+
}
|
|
316
|
+
isEnabled() {
|
|
317
|
+
return this.call('isEnabled');
|
|
318
|
+
}
|
|
319
|
+
hasShadow() {
|
|
320
|
+
return this.call('hasShadow');
|
|
321
|
+
}
|
|
322
|
+
isVisibleOnAllWorkspaces() {
|
|
323
|
+
return this.call('isVisibleOnAllWorkspaces');
|
|
324
|
+
}
|
|
325
|
+
setKiosk(flag) {
|
|
326
|
+
return this.call('setKiosk', [flag]);
|
|
327
|
+
}
|
|
328
|
+
isKiosk() {
|
|
329
|
+
return this.call('isKiosk');
|
|
330
|
+
}
|
|
331
|
+
// 视觉提醒 / 任务栏 / 菜单栏
|
|
332
|
+
flashFrame(flag) {
|
|
333
|
+
return this.call('flashFrame', [flag]);
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* 任务栏进度条。progress:[0,1] 显示进度;-1 关闭;>1 不确定。
|
|
337
|
+
* options.mode: 'none' | 'normal' | 'indeterminate' | 'error' | 'paused'。
|
|
338
|
+
*/
|
|
339
|
+
setProgressBar(progress, options) {
|
|
340
|
+
return this.call('setProgressBar', options === undefined ? [progress] : [progress, options]);
|
|
341
|
+
}
|
|
342
|
+
setMenuBarVisibility(visible) {
|
|
343
|
+
return this.call('setMenuBarVisibility', [visible]);
|
|
344
|
+
}
|
|
345
|
+
isMenuBarVisible() {
|
|
346
|
+
return this.call('isMenuBarVisible');
|
|
347
|
+
}
|
|
348
|
+
setAutoHideMenuBar(hide) {
|
|
349
|
+
return this.call('setAutoHideMenuBar', [hide]);
|
|
350
|
+
}
|
|
351
|
+
isMenuBarAutoHide() {
|
|
352
|
+
return this.call('isMenuBarAutoHide');
|
|
353
|
+
}
|
|
354
|
+
removeMenu() {
|
|
355
|
+
return this.call('removeMenu');
|
|
356
|
+
}
|
|
357
|
+
/** macOS 透明窗口在内容大小变化后需手动失效阴影。 */
|
|
358
|
+
invalidateShadow() {
|
|
359
|
+
return this.call('invalidateShadow');
|
|
360
|
+
}
|
|
361
|
+
// macOS 文档窗口标识
|
|
362
|
+
setRepresentedFilename(path) {
|
|
363
|
+
return this.call('setRepresentedFilename', [path]);
|
|
364
|
+
}
|
|
365
|
+
getRepresentedFilename() {
|
|
366
|
+
return this.call('getRepresentedFilename');
|
|
367
|
+
}
|
|
368
|
+
setDocumentEdited(edited) {
|
|
369
|
+
return this.call('setDocumentEdited', [edited]);
|
|
370
|
+
}
|
|
371
|
+
isDocumentEdited() {
|
|
372
|
+
return this.call('isDocumentEdited');
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* 类 Electron 风格的 webContents 子对象。等价于直接调用 `webContentsSend` 等方法,
|
|
376
|
+
* 但写法更接近 uTools / Electron 原生:`win.webContents.send('ch', payload)`。
|
|
377
|
+
*/
|
|
378
|
+
webContents = {
|
|
379
|
+
send: (channel, ...args) => this.sendToWebContents(channel, args),
|
|
380
|
+
executeJavaScript: (code, userGesture) => this.call('webContents.executeJavaScript', userGesture === undefined ? [code] : [code, userGesture]),
|
|
381
|
+
openDevTools: (options) => this.call('webContents.openDevTools', options === undefined ? [] : [options]),
|
|
382
|
+
closeDevTools: () => this.call('webContents.closeDevTools'),
|
|
383
|
+
toggleDevTools: () => this.call('webContents.toggleDevTools'),
|
|
384
|
+
isDevToolsOpened: () => this.call('webContents.isDevToolsOpened'),
|
|
385
|
+
// 导航
|
|
386
|
+
goBack: () => this.call('webContents.goBack'),
|
|
387
|
+
goForward: () => this.call('webContents.goForward'),
|
|
388
|
+
canGoBack: () => this.call('webContents.canGoBack'),
|
|
389
|
+
canGoForward: () => this.call('webContents.canGoForward'),
|
|
390
|
+
getURL: () => this.call('webContents.getURL'),
|
|
391
|
+
getTitle: () => this.call('webContents.getTitle'),
|
|
392
|
+
// 缩放
|
|
393
|
+
setZoomFactor: (factor) => this.call('webContents.setZoomFactor', [factor]),
|
|
394
|
+
getZoomFactor: () => this.call('webContents.getZoomFactor'),
|
|
395
|
+
setZoomLevel: (level) => this.call('webContents.setZoomLevel', [level]),
|
|
396
|
+
getZoomLevel: () => this.call('webContents.getZoomLevel'),
|
|
397
|
+
// 编辑命令
|
|
398
|
+
copy: () => this.call('webContents.copy'),
|
|
399
|
+
paste: () => this.call('webContents.paste'),
|
|
400
|
+
cut: () => this.call('webContents.cut'),
|
|
401
|
+
selectAll: () => this.call('webContents.selectAll'),
|
|
402
|
+
undo: () => this.call('webContents.undo'),
|
|
403
|
+
redo: () => this.call('webContents.redo')
|
|
404
|
+
};
|
|
405
|
+
/** webContents.send 旧别名,保留向后兼容。新代码请用 `win.webContents.send(...)`。 */
|
|
406
|
+
webContentsSend(channel, ...args) {
|
|
407
|
+
return this.webContents.send(channel, ...args);
|
|
408
|
+
}
|
|
409
|
+
/** 关闭窗口(不可逆)。走 host.ui.closeWindow,而非白名单 close 方法。 */
|
|
410
|
+
async close() {
|
|
411
|
+
if (this.closed)
|
|
412
|
+
return;
|
|
413
|
+
this.closed = true;
|
|
414
|
+
await this.transport.hostCall({
|
|
415
|
+
type: 'host.ui.closeWindow',
|
|
416
|
+
windowId: this.id
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
/** 内部:由 BricklyRuntime 在收到对应 window.* 事件时调用。 */
|
|
420
|
+
_emit(eventName, payload) {
|
|
421
|
+
if (eventName === 'closed')
|
|
422
|
+
this.closed = true;
|
|
423
|
+
this.emit(eventName, payload);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
exports.WindowHandle = WindowHandle;
|
|
427
|
+
function explicitPayloadRequestId(args) {
|
|
428
|
+
const payload = args[0];
|
|
429
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload))
|
|
430
|
+
return undefined;
|
|
431
|
+
const requestId = payload.requestId;
|
|
432
|
+
return typeof requestId === 'string' && requestId ? requestId : undefined;
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Brick SDK 主入口。典型用法:
|
|
436
|
+
*
|
|
437
|
+
* const brick = new BricklyRuntime({ brickId: 'com.example.foo' })
|
|
438
|
+
* brick.onCommand('do-it', async (ctx, input) => { ... })
|
|
439
|
+
* brick.onReady(async () => { ... })
|
|
440
|
+
* brick.start()
|
|
441
|
+
*/
|
|
442
|
+
class BricklyRuntime {
|
|
443
|
+
brickId;
|
|
444
|
+
transport;
|
|
445
|
+
protocolVersion;
|
|
446
|
+
commandHandlers = new Map();
|
|
447
|
+
cancelHandlers = new Map();
|
|
448
|
+
cancelled = new Set();
|
|
449
|
+
eventListeners = new Map();
|
|
450
|
+
windows = new Map();
|
|
451
|
+
readyHandler;
|
|
452
|
+
shutdownHandler;
|
|
453
|
+
config = {};
|
|
454
|
+
started = false;
|
|
455
|
+
exiting = false;
|
|
456
|
+
hostPidWatchdog;
|
|
457
|
+
constructor(opts) {
|
|
458
|
+
this.brickId = opts.brickId;
|
|
459
|
+
this.protocolVersion = opts.protocolVersion ?? protocol_1.PROTOCOL_VERSION;
|
|
460
|
+
this.transport = new runtime_1.BppTransport(opts);
|
|
461
|
+
this.transport.on('error', (err) => this.transport.log('transport error:', String(err)));
|
|
462
|
+
this.transport.on('message', (msg) => this.handleHello(msg));
|
|
463
|
+
this.transport.on('command', (msg) => this.handleCommand(msg));
|
|
464
|
+
this.transport.on('event', (msg) => this.handleEvent(msg));
|
|
465
|
+
this.transport.on('shutdown', () => void this.handleShutdown());
|
|
466
|
+
this.transport.on('host-disconnect', () => void this.handleHostDisconnect());
|
|
467
|
+
}
|
|
468
|
+
/** 注册 command 处理器。 */
|
|
469
|
+
onCommand(commandId, handler) {
|
|
470
|
+
this.commandHandlers.set(commandId, handler);
|
|
471
|
+
return this;
|
|
472
|
+
}
|
|
473
|
+
/** 注册 runtime.ready 之后立即执行的钩子(适合 host-start service 或实例初始化逻辑)。 */
|
|
474
|
+
onReady(handler) {
|
|
475
|
+
this.readyHandler = handler;
|
|
476
|
+
return this;
|
|
477
|
+
}
|
|
478
|
+
/** 注册 runtime.shutdown 处理逻辑;返回后 SDK 自动发送 runtime.bye 并退出。 */
|
|
479
|
+
onShutdown(handler) {
|
|
480
|
+
this.shutdownHandler = handler;
|
|
481
|
+
return this;
|
|
482
|
+
}
|
|
483
|
+
/** 子窗口 / UI API。 */
|
|
484
|
+
ui = {
|
|
485
|
+
createBrowserWindow: async (url, options) => {
|
|
486
|
+
const result = await this.transport.hostCall({
|
|
487
|
+
type: 'host.ui.createBrowserWindow',
|
|
488
|
+
url,
|
|
489
|
+
options
|
|
490
|
+
});
|
|
491
|
+
const handle = new WindowHandle(this.transport, result.windowId);
|
|
492
|
+
this.windows.set(result.windowId, handle);
|
|
493
|
+
handle.once('closed', () => this.windows.delete(result.windowId));
|
|
494
|
+
return handle;
|
|
495
|
+
},
|
|
496
|
+
listWindows: () => this.transport.hostCall({
|
|
497
|
+
type: 'host.ui.listWindows'
|
|
498
|
+
})
|
|
499
|
+
};
|
|
500
|
+
/** 事件订阅 API。 */
|
|
501
|
+
events = {
|
|
502
|
+
on: (event, handler) => {
|
|
503
|
+
let set = this.eventListeners.get(event);
|
|
504
|
+
if (!set) {
|
|
505
|
+
set = new Set();
|
|
506
|
+
this.eventListeners.set(event, set);
|
|
507
|
+
}
|
|
508
|
+
set.add(handler);
|
|
509
|
+
return () => {
|
|
510
|
+
set.delete(handler);
|
|
511
|
+
if (set.size === 0)
|
|
512
|
+
this.eventListeners.delete(event);
|
|
513
|
+
};
|
|
514
|
+
},
|
|
515
|
+
publish: (event, payload) => this.transport.hostCall({
|
|
516
|
+
type: 'host.event.publish',
|
|
517
|
+
event,
|
|
518
|
+
payload
|
|
519
|
+
})
|
|
520
|
+
};
|
|
521
|
+
/** 宿主平台能力 API。 */
|
|
522
|
+
platform = {
|
|
523
|
+
screenshot: {
|
|
524
|
+
selectRegion: (options) => this.transport.hostCall({
|
|
525
|
+
type: 'host.platform.screenshot.selectRegion',
|
|
526
|
+
options
|
|
527
|
+
})
|
|
528
|
+
},
|
|
529
|
+
screen: {
|
|
530
|
+
captureRegion: (options) => this.transport.hostCall({
|
|
531
|
+
type: 'host.platform.screen.captureRegion',
|
|
532
|
+
options
|
|
533
|
+
}),
|
|
534
|
+
pickColor: (options) => this.transport.hostCall({
|
|
535
|
+
type: 'host.platform.screen.pickColor',
|
|
536
|
+
options
|
|
537
|
+
}),
|
|
538
|
+
getPrimaryDisplay: () => this.transport.hostCall({
|
|
539
|
+
type: 'host.platform.screen.getPrimaryDisplay'
|
|
540
|
+
}),
|
|
541
|
+
getAllDisplays: () => this.transport.hostCall({
|
|
542
|
+
type: 'host.platform.screen.getAllDisplays'
|
|
543
|
+
}),
|
|
544
|
+
getCursorScreenPoint: () => this.transport.hostCall({
|
|
545
|
+
type: 'host.platform.screen.getCursorScreenPoint'
|
|
546
|
+
}),
|
|
547
|
+
getDisplayNearestPoint: (point) => this.transport.hostCall({
|
|
548
|
+
type: 'host.platform.screen.getDisplayNearestPoint',
|
|
549
|
+
point
|
|
550
|
+
}),
|
|
551
|
+
getDisplayMatching: (rect) => this.transport.hostCall({
|
|
552
|
+
type: 'host.platform.screen.getDisplayMatching',
|
|
553
|
+
rect
|
|
554
|
+
}),
|
|
555
|
+
screenToDipPoint: (point) => this.transport.hostCall({
|
|
556
|
+
type: 'host.platform.screen.screenToDipPoint',
|
|
557
|
+
point
|
|
558
|
+
}),
|
|
559
|
+
dipToScreenPoint: (point) => this.transport.hostCall({
|
|
560
|
+
type: 'host.platform.screen.dipToScreenPoint',
|
|
561
|
+
point
|
|
562
|
+
}),
|
|
563
|
+
screenToDipRect: (rect) => this.transport.hostCall({
|
|
564
|
+
type: 'host.platform.screen.screenToDipRect',
|
|
565
|
+
rect
|
|
566
|
+
}),
|
|
567
|
+
dipToScreenRect: (rect) => this.transport.hostCall({
|
|
568
|
+
type: 'host.platform.screen.dipToScreenRect',
|
|
569
|
+
rect
|
|
570
|
+
}),
|
|
571
|
+
desktopCaptureSources: (options) => this.transport.hostCall({
|
|
572
|
+
type: 'host.platform.screen.desktopCaptureSources',
|
|
573
|
+
options
|
|
574
|
+
})
|
|
575
|
+
},
|
|
576
|
+
input: {
|
|
577
|
+
keyboardTap: (keyOrPayload, ...modifiers) => this.transport.hostCall({
|
|
578
|
+
type: 'host.platform.input.keyboardTap',
|
|
579
|
+
payload: typeof keyOrPayload === 'string' ? { key: keyOrPayload, modifiers } : keyOrPayload
|
|
580
|
+
}),
|
|
581
|
+
mouseMove: (xOrPayload, y) => this.transport.hostCall({
|
|
582
|
+
type: 'host.platform.input.mouseMove',
|
|
583
|
+
payload: pointPayload(xOrPayload, y)
|
|
584
|
+
}),
|
|
585
|
+
mouseClick: (xOrPayload, y) => this.transport.hostCall({
|
|
586
|
+
type: 'host.platform.input.mouseClick',
|
|
587
|
+
payload: pointPayload(xOrPayload, y)
|
|
588
|
+
}),
|
|
589
|
+
mouseDoubleClick: (xOrPayload, y) => this.transport.hostCall({
|
|
590
|
+
type: 'host.platform.input.mouseDoubleClick',
|
|
591
|
+
payload: pointPayload(xOrPayload, y)
|
|
592
|
+
}),
|
|
593
|
+
mouseRightClick: (xOrPayload, y) => this.transport.hostCall({
|
|
594
|
+
type: 'host.platform.input.mouseRightClick',
|
|
595
|
+
payload: pointPayload(xOrPayload, y)
|
|
596
|
+
})
|
|
597
|
+
},
|
|
598
|
+
clipboard: {
|
|
599
|
+
readContent: () => this.transport.hostCall({
|
|
600
|
+
type: 'host.platform.clipboard.readContent'
|
|
601
|
+
}),
|
|
602
|
+
setContent: (content) => this.transport.hostCall({
|
|
603
|
+
type: 'host.platform.clipboard.setContent',
|
|
604
|
+
content
|
|
605
|
+
})
|
|
606
|
+
},
|
|
607
|
+
system: {
|
|
608
|
+
showNotification: (body, clickFeatureCode) => this.transport.hostCall({
|
|
609
|
+
type: 'host.platform.system.showNotification',
|
|
610
|
+
body,
|
|
611
|
+
...(clickFeatureCode ? { clickFeatureCode } : {})
|
|
612
|
+
}),
|
|
613
|
+
shellOpenPath: (fullPath) => this.transport.hostCall({
|
|
614
|
+
type: 'host.platform.system.shellOpenPath',
|
|
615
|
+
fullPath
|
|
616
|
+
}),
|
|
617
|
+
shellTrashItem: (fullPath) => this.transport.hostCall({
|
|
618
|
+
type: 'host.platform.system.shellTrashItem',
|
|
619
|
+
fullPath
|
|
620
|
+
}),
|
|
621
|
+
shellShowItemInFolder: (fullPath) => this.transport.hostCall({
|
|
622
|
+
type: 'host.platform.system.shellShowItemInFolder',
|
|
623
|
+
fullPath
|
|
624
|
+
}),
|
|
625
|
+
shellOpenExternal: (url) => this.transport.hostCall({
|
|
626
|
+
type: 'host.platform.system.shellOpenExternal',
|
|
627
|
+
url
|
|
628
|
+
}),
|
|
629
|
+
shellBeep: () => this.transport.hostCall({
|
|
630
|
+
type: 'host.platform.system.shellBeep'
|
|
631
|
+
}),
|
|
632
|
+
getNativeId: () => this.transport.hostCall({
|
|
633
|
+
type: 'host.platform.system.getNativeId'
|
|
634
|
+
}),
|
|
635
|
+
getAppName: () => this.transport.hostCall({
|
|
636
|
+
type: 'host.platform.system.getAppName'
|
|
637
|
+
}),
|
|
638
|
+
getAppVersion: () => this.transport.hostCall({
|
|
639
|
+
type: 'host.platform.system.getAppVersion'
|
|
640
|
+
}),
|
|
641
|
+
getPath: (name) => this.transport.hostCall({
|
|
642
|
+
type: 'host.platform.system.getPath',
|
|
643
|
+
name
|
|
644
|
+
}),
|
|
645
|
+
getFileIcon: (filePath) => this.transport.hostCall({
|
|
646
|
+
type: 'host.platform.system.getFileIcon',
|
|
647
|
+
filePath
|
|
648
|
+
}),
|
|
649
|
+
readCurrentFolderPath: () => this.transport.hostCall({
|
|
650
|
+
type: 'host.platform.system.readCurrentFolderPath'
|
|
651
|
+
}),
|
|
652
|
+
readCurrentBrowserUrl: () => this.transport.hostCall({
|
|
653
|
+
type: 'host.platform.system.readCurrentBrowserUrl'
|
|
654
|
+
}),
|
|
655
|
+
isDev: () => this.transport.hostCall({
|
|
656
|
+
type: 'host.platform.system.isDev'
|
|
657
|
+
}),
|
|
658
|
+
isMacOS: () => this.transport.hostCall({
|
|
659
|
+
type: 'host.platform.system.isMacOS'
|
|
660
|
+
}),
|
|
661
|
+
isWindows: () => this.transport.hostCall({
|
|
662
|
+
type: 'host.platform.system.isWindows'
|
|
663
|
+
}),
|
|
664
|
+
isLinux: () => this.transport.hostCall({
|
|
665
|
+
type: 'host.platform.system.isLinux'
|
|
666
|
+
})
|
|
667
|
+
}
|
|
668
|
+
};
|
|
669
|
+
/** 启动 stdin 循环。 */
|
|
670
|
+
start() {
|
|
671
|
+
if (this.started)
|
|
672
|
+
return this;
|
|
673
|
+
this.started = true;
|
|
674
|
+
this.transport.start();
|
|
675
|
+
this.startHostPidWatchdog();
|
|
676
|
+
return this;
|
|
677
|
+
}
|
|
678
|
+
/** 停止 stdin 循环(一般不需要主动调,runtime.shutdown 会自动处理)。 */
|
|
679
|
+
stop() {
|
|
680
|
+
if (this.hostPidWatchdog) {
|
|
681
|
+
clearInterval(this.hostPidWatchdog);
|
|
682
|
+
this.hostPidWatchdog = undefined;
|
|
683
|
+
}
|
|
684
|
+
this.transport.stop('brick stopped');
|
|
685
|
+
}
|
|
686
|
+
invoke(brickId, commandId, input = null, options) {
|
|
687
|
+
const parentRequestId = currentCommandScope()?.requestId;
|
|
688
|
+
if (!parentRequestId) {
|
|
689
|
+
return Promise.reject(new errors_1.BppError('PARENT_INVOCATION_REQUIRED', 'invoke() must run inside command handler; use invokeRoot() for root calls'));
|
|
690
|
+
}
|
|
691
|
+
return this.transport.hostCall({
|
|
692
|
+
type: 'host.invoke',
|
|
693
|
+
brickId,
|
|
694
|
+
commandId,
|
|
695
|
+
input,
|
|
696
|
+
parentRequestId,
|
|
697
|
+
...(options?.profileId ? { profileId: options.profileId } : {})
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
invokeRoot(brickId, commandId, input = null, options) {
|
|
701
|
+
return this.transport.hostCall({
|
|
702
|
+
type: 'host.invokeRoot',
|
|
703
|
+
brickId,
|
|
704
|
+
commandId,
|
|
705
|
+
input,
|
|
706
|
+
...(options?.profileId ? { profileId: options.profileId } : {})
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
invokeStream(brickId, commandId, input = null, options) {
|
|
710
|
+
return this.hostInvokeStream(brickId, commandId, input, options);
|
|
711
|
+
}
|
|
712
|
+
hostInvokeStream(brickId, commandId, input, options) {
|
|
713
|
+
const parentRequestId = currentCommandScope()?.requestId;
|
|
714
|
+
if (!parentRequestId) {
|
|
715
|
+
throw new errors_1.BppError('PARENT_INVOCATION_REQUIRED', 'invokeStream() must run inside command handler');
|
|
716
|
+
}
|
|
717
|
+
const transport = this.transport;
|
|
718
|
+
const id = transport.nextHostId();
|
|
719
|
+
const queue = [];
|
|
720
|
+
const waiters = [];
|
|
721
|
+
let done = false;
|
|
722
|
+
let cleanedUp = false;
|
|
723
|
+
const push = (event, finish = false) => {
|
|
724
|
+
queue.push(event);
|
|
725
|
+
done ||= finish;
|
|
726
|
+
const waiter = waiters.shift();
|
|
727
|
+
if (waiter)
|
|
728
|
+
waiter({ value: queue.shift(), done: false });
|
|
729
|
+
};
|
|
730
|
+
const pushError = (error) => {
|
|
731
|
+
push({ type: 'error', error: errors_1.BppError.from(error) }, true);
|
|
732
|
+
cleanup();
|
|
733
|
+
};
|
|
734
|
+
const onMessage = (msg) => {
|
|
735
|
+
if (!('id' in msg) || msg.id !== id)
|
|
736
|
+
return;
|
|
737
|
+
if (msg.type === 'host.invoke.progress') {
|
|
738
|
+
push({ type: 'progress', progress: msg.progress, message: msg.message });
|
|
739
|
+
}
|
|
740
|
+
else if (msg.type === 'host.invoke.chunk') {
|
|
741
|
+
push({ type: 'chunk', name: msg.name, chunk: msg.chunk });
|
|
742
|
+
}
|
|
743
|
+
else if (msg.type === 'host.invoke.output') {
|
|
744
|
+
push({ type: 'output', name: msg.name, value: msg.value });
|
|
745
|
+
}
|
|
746
|
+
else if (msg.type === 'host.result') {
|
|
747
|
+
push({ type: 'result', result: msg.result }, true);
|
|
748
|
+
cleanup();
|
|
749
|
+
}
|
|
750
|
+
else if (msg.type === 'host.error') {
|
|
751
|
+
push({ type: 'error', error: (0, errors_1.payloadToError)(msg.error) }, true);
|
|
752
|
+
cleanup();
|
|
753
|
+
}
|
|
754
|
+
};
|
|
755
|
+
const onEnd = () => {
|
|
756
|
+
pushError(new errors_1.BppError('PROCESS_EXITED', 'host pipe closed'));
|
|
757
|
+
};
|
|
758
|
+
const onError = (err) => {
|
|
759
|
+
pushError(new errors_1.BppError('PROCESS_EXITED', 'host pipe error', { error: err }));
|
|
760
|
+
};
|
|
761
|
+
const cleanup = () => {
|
|
762
|
+
if (cleanedUp)
|
|
763
|
+
return;
|
|
764
|
+
cleanedUp = true;
|
|
765
|
+
transport.off('message', onMessage);
|
|
766
|
+
transport.off('end', onEnd);
|
|
767
|
+
transport.off('error', onError);
|
|
768
|
+
};
|
|
769
|
+
transport.on('message', onMessage);
|
|
770
|
+
transport.once('end', onEnd);
|
|
771
|
+
transport.once('error', onError);
|
|
772
|
+
transport.send({
|
|
773
|
+
type: 'host.invoke',
|
|
774
|
+
id,
|
|
775
|
+
brickId,
|
|
776
|
+
commandId,
|
|
777
|
+
input,
|
|
778
|
+
parentRequestId,
|
|
779
|
+
stream: true,
|
|
780
|
+
...(options?.profileId ? { profileId: options.profileId } : {})
|
|
781
|
+
});
|
|
782
|
+
return {
|
|
783
|
+
[Symbol.asyncIterator]() {
|
|
784
|
+
return {
|
|
785
|
+
next: () => {
|
|
786
|
+
const event = queue.shift();
|
|
787
|
+
if (event)
|
|
788
|
+
return Promise.resolve({ value: event, done: false });
|
|
789
|
+
if (done)
|
|
790
|
+
return Promise.resolve({ value: undefined, done: true });
|
|
791
|
+
return new Promise((resolve) => waiters.push(resolve));
|
|
792
|
+
},
|
|
793
|
+
return: () => {
|
|
794
|
+
done = true;
|
|
795
|
+
cleanup();
|
|
796
|
+
return Promise.resolve({ value: undefined, done: true });
|
|
797
|
+
}
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
async openSession(brickId, options) {
|
|
803
|
+
const result = await this.transport.hostCall({
|
|
804
|
+
type: 'host.session.open',
|
|
805
|
+
brickId,
|
|
806
|
+
...(options?.profileId ? { profileId: options.profileId } : {})
|
|
807
|
+
});
|
|
808
|
+
return new BrickSession(this.transport, result.sessionId, result.brickId, result.profileId);
|
|
809
|
+
}
|
|
810
|
+
// —— 内部分发 ——
|
|
811
|
+
handleHello(msg) {
|
|
812
|
+
if (msg.type !== 'host.hello')
|
|
813
|
+
return;
|
|
814
|
+
this.config = msg.config && typeof msg.config === 'object' ? msg.config : {};
|
|
815
|
+
this.transport.send({
|
|
816
|
+
type: 'runtime.ready',
|
|
817
|
+
protocolVersion: this.protocolVersion,
|
|
818
|
+
brickId: this.brickId
|
|
819
|
+
});
|
|
820
|
+
if (this.readyHandler) {
|
|
821
|
+
Promise.resolve()
|
|
822
|
+
.then(() => this.readyHandler?.())
|
|
823
|
+
.catch((err) => this.transport.log('onReady error:', errors_1.BppError.from(err).message));
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
handleCommand(msg) {
|
|
827
|
+
if (msg.type === 'command.cancel') {
|
|
828
|
+
this.cancelled.add(msg.id);
|
|
829
|
+
const fn = this.cancelHandlers.get(msg.id);
|
|
830
|
+
if (fn) {
|
|
831
|
+
try {
|
|
832
|
+
fn();
|
|
833
|
+
}
|
|
834
|
+
catch (err) {
|
|
835
|
+
this.transport.log('cancel handler error:', errors_1.BppError.from(err).message);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
if (msg.type !== 'command.invoke')
|
|
841
|
+
return;
|
|
842
|
+
const handler = this.commandHandlers.get(msg.commandId);
|
|
843
|
+
const reqId = msg.id;
|
|
844
|
+
if (!handler) {
|
|
845
|
+
this.transport.send({
|
|
846
|
+
type: 'command.error',
|
|
847
|
+
id: reqId,
|
|
848
|
+
error: {
|
|
849
|
+
code: 'COMMAND_NOT_FOUND',
|
|
850
|
+
message: `Unknown command: ${msg.commandId}`
|
|
851
|
+
}
|
|
852
|
+
});
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
const ctx = this.buildContext(reqId, msg.commandId, msg.invocation);
|
|
856
|
+
Promise.resolve()
|
|
857
|
+
.then(() => commandScope.run({ requestId: reqId }, () => handler(ctx, msg.input)))
|
|
858
|
+
.then((result) => {
|
|
859
|
+
this.transport.send({ type: 'command.result', id: reqId, result });
|
|
860
|
+
})
|
|
861
|
+
.catch((err) => {
|
|
862
|
+
const e = errors_1.BppError.from(err);
|
|
863
|
+
this.transport.send({ type: 'command.error', id: reqId, error: e.toJSON() });
|
|
864
|
+
})
|
|
865
|
+
.finally(() => {
|
|
866
|
+
this.cancelled.delete(reqId);
|
|
867
|
+
this.cancelHandlers.delete(reqId);
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
handleEvent(msg) {
|
|
871
|
+
if (msg.type !== 'event.notify')
|
|
872
|
+
return;
|
|
873
|
+
const env = {
|
|
874
|
+
event: msg.event,
|
|
875
|
+
payload: msg.payload,
|
|
876
|
+
sourceBrickId: msg.sourceBrickId,
|
|
877
|
+
publishedAt: msg.publishedAt
|
|
878
|
+
};
|
|
879
|
+
// 转发到 WindowHandle:window.* 事件如果 payload.windowId 命中已知窗口,路由到句柄。
|
|
880
|
+
if (msg.event.startsWith('window.')) {
|
|
881
|
+
const wid = msg.payload?.windowId;
|
|
882
|
+
if (typeof wid === 'number') {
|
|
883
|
+
const handle = this.windows.get(wid);
|
|
884
|
+
if (handle) {
|
|
885
|
+
const subEvent = msg.event.slice('window.'.length);
|
|
886
|
+
handle._emit(subEvent, msg.payload);
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
const set = this.eventListeners.get(msg.event);
|
|
891
|
+
if (set) {
|
|
892
|
+
for (const fn of set) {
|
|
893
|
+
try {
|
|
894
|
+
fn(msg.payload, env);
|
|
895
|
+
}
|
|
896
|
+
catch (err) {
|
|
897
|
+
this.transport.log('event handler error:', errors_1.BppError.from(err).message);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
async handleShutdown() {
|
|
903
|
+
if (this.exiting)
|
|
904
|
+
return;
|
|
905
|
+
this.exiting = true;
|
|
906
|
+
try {
|
|
907
|
+
if (this.shutdownHandler)
|
|
908
|
+
await this.shutdownHandler();
|
|
909
|
+
}
|
|
910
|
+
catch (err) {
|
|
911
|
+
this.transport.log('shutdown handler error:', errors_1.BppError.from(err).message);
|
|
912
|
+
}
|
|
913
|
+
this.transport.send({ type: 'runtime.bye' });
|
|
914
|
+
// 留一点时间把字节冲出去再退出
|
|
915
|
+
setTimeout(() => process.exit(0), 50).unref?.();
|
|
916
|
+
}
|
|
917
|
+
async handleHostDisconnect() {
|
|
918
|
+
if (this.exiting)
|
|
919
|
+
return;
|
|
920
|
+
this.exiting = true;
|
|
921
|
+
this.transport.log('host disconnected; exiting brick runtime');
|
|
922
|
+
try {
|
|
923
|
+
if (this.shutdownHandler)
|
|
924
|
+
await this.shutdownHandler();
|
|
925
|
+
}
|
|
926
|
+
catch (err) {
|
|
927
|
+
this.transport.log('host disconnect cleanup error:', errors_1.BppError.from(err).message);
|
|
928
|
+
}
|
|
929
|
+
setTimeout(() => process.exit(0), 50).unref?.();
|
|
930
|
+
}
|
|
931
|
+
startHostPidWatchdog() {
|
|
932
|
+
const rawPid = process.env.BRICKLY_HOST_PID;
|
|
933
|
+
if (!rawPid)
|
|
934
|
+
return;
|
|
935
|
+
const hostPid = Number(rawPid);
|
|
936
|
+
if (!Number.isInteger(hostPid) || hostPid <= 0)
|
|
937
|
+
return;
|
|
938
|
+
this.hostPidWatchdog = setInterval(() => {
|
|
939
|
+
try {
|
|
940
|
+
process.kill(hostPid, 0);
|
|
941
|
+
}
|
|
942
|
+
catch {
|
|
943
|
+
if (this.hostPidWatchdog) {
|
|
944
|
+
clearInterval(this.hostPidWatchdog);
|
|
945
|
+
this.hostPidWatchdog = undefined;
|
|
946
|
+
}
|
|
947
|
+
void this.handleHostDisconnect();
|
|
948
|
+
}
|
|
949
|
+
}, 5000);
|
|
950
|
+
this.hostPidWatchdog.unref?.();
|
|
951
|
+
}
|
|
952
|
+
buildContext(reqId, commandId, invocation) {
|
|
953
|
+
const resolvedInvocation = invocation ?? { source: 'unknown' };
|
|
954
|
+
const withInvocationProfile = (brickId, options) => {
|
|
955
|
+
if (options?.profileId)
|
|
956
|
+
return options;
|
|
957
|
+
const profileId = resolvedInvocation.dependencyProfiles?.[brickId];
|
|
958
|
+
if (!profileId)
|
|
959
|
+
return options;
|
|
960
|
+
return { ...(options ?? {}), profileId };
|
|
961
|
+
};
|
|
962
|
+
const invoke = ((brickId, targetCommandId, input, options) => this.invoke(brickId, targetCommandId, input, withInvocationProfile(brickId, options)));
|
|
963
|
+
const openSession = ((brickId, options) => this.openSession(brickId, withInvocationProfile(brickId, options)));
|
|
964
|
+
const invokeStream = ((brickId, targetCommandId, input, options) => this.hostInvokeStream(brickId, targetCommandId, input, withInvocationProfile(brickId, options)));
|
|
965
|
+
return {
|
|
966
|
+
requestId: reqId,
|
|
967
|
+
commandId,
|
|
968
|
+
invocation: resolvedInvocation,
|
|
969
|
+
onCancel: (fn) => {
|
|
970
|
+
this.cancelHandlers.set(reqId, fn);
|
|
971
|
+
// 如果取消信号已经到了,立刻触发
|
|
972
|
+
if (this.cancelled.has(reqId)) {
|
|
973
|
+
try {
|
|
974
|
+
fn();
|
|
975
|
+
}
|
|
976
|
+
catch {
|
|
977
|
+
/* ignore */
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
},
|
|
981
|
+
isCancelled: () => this.cancelled.has(reqId),
|
|
982
|
+
progress: (value, message) => this.transport.send({ type: 'command.progress', id: reqId, progress: value, message }),
|
|
983
|
+
chunk: (chunk, name) => this.transport.send({ type: 'command.chunk', id: reqId, name, chunk }),
|
|
984
|
+
output: (name, value) => this.transport.send({ type: 'command.output', id: reqId, name, value }),
|
|
985
|
+
invoke,
|
|
986
|
+
invokeStream,
|
|
987
|
+
openSession,
|
|
988
|
+
ui: this.ui,
|
|
989
|
+
events: this.events,
|
|
990
|
+
platform: this.platform,
|
|
991
|
+
config: this.config
|
|
992
|
+
};
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
exports.BricklyRuntime = BricklyRuntime;
|