bunite-core 0.17.1 → 0.17.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/package.json +1 -1
- package/src/host/core/App.ts +28 -18
- package/src/host/core/BrowserView.ts +333 -116
- package/src/host/core/BrowserWindow.ts +44 -22
- package/src/host/core/Socket.ts +26 -9
- package/src/host/core/SurfaceBrowserIPC.ts +15 -5
- package/src/host/core/SurfaceManager.ts +345 -158
- package/src/host/core/SurfaceRegistry.ts +1 -1
- package/src/host/core/inputDispatch.ts +71 -42
- package/src/host/core/singleInstanceLock.ts +10 -2
- package/src/host/core/windowCap.ts +42 -23
- package/src/host/encryptedPipe.ts +12 -3
- package/src/host/events/appEvents.ts +1 -1
- package/src/host/events/eventEmitter.ts +6 -6
- package/src/host/events/webviewEvents.ts +11 -4
- package/src/host/events/windowEvents.ts +1 -2
- package/src/host/index.ts +9 -17
- package/src/host/log.ts +6 -4
- package/src/host/native.ts +271 -163
- package/src/host/paths.ts +18 -15
- package/src/host/platform.ts +2 -4
- package/src/host/preloadBundle.ts +1 -1
- package/src/host/serveWeb.ts +18 -11
- package/src/preload/runtime.built.js +1 -1
- package/src/preload/runtime.ts +61 -24
- package/src/rpc/encrypt.ts +15 -4
- package/src/rpc/error.ts +2 -7
- package/src/rpc/framework.ts +105 -35
- package/src/rpc/index.ts +129 -140
- package/src/rpc/peer.ts +430 -159
- package/src/rpc/renderer.ts +15 -9
- package/src/rpc/schema.ts +86 -61
- package/src/rpc/stream.ts +12 -3
- package/src/rpc/transport.ts +28 -9
- package/src/rpc/wire.ts +23 -10
- package/src/webview/native.ts +150 -73
- package/src/webview/polyfill.ts +184 -48
package/src/webview/native.ts
CHANGED
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
// <bunite-webview> custom element — registered in every appres:// page via preload.
|
|
2
2
|
|
|
3
|
-
import type { ClientOf } from "../rpc/index";
|
|
4
3
|
import type {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
DownloadEvent,
|
|
4
|
+
AccessibilitySnapshotResult,
|
|
5
|
+
BoundingRectResult,
|
|
6
|
+
ConsoleEntry,
|
|
7
|
+
DownloadEvent,
|
|
8
|
+
DownloadPolicy,
|
|
9
|
+
EvaluateResult,
|
|
10
|
+
ListFramesResult,
|
|
11
|
+
NavigationState,
|
|
9
12
|
ResolveAndClickResult,
|
|
13
|
+
ScreenshotResult,
|
|
14
|
+
SurfaceCap,
|
|
15
|
+
SurfaceCapabilities,
|
|
16
|
+
SurfaceEvent,
|
|
17
|
+
WaitForDownloadResult,
|
|
18
|
+
WaitResult,
|
|
10
19
|
} from "../rpc/framework";
|
|
20
|
+
import type { ClientOf } from "../rpc/index";
|
|
11
21
|
|
|
12
22
|
declare const __buniteWebviewId: number;
|
|
13
23
|
|
|
@@ -25,12 +35,14 @@ function getSurfaceCap(): Promise<SurfaceClient> {
|
|
|
25
35
|
}
|
|
26
36
|
|
|
27
37
|
function callSurface<R>(fn: (s: SurfaceClient) => Promise<R> | R): Promise<R | void> {
|
|
28
|
-
return getSurfaceCap()
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
38
|
+
return getSurfaceCap()
|
|
39
|
+
.then(fn)
|
|
40
|
+
.catch((err) => {
|
|
41
|
+
if ((globalThis as { __BUNITE_DEBUG__?: boolean }).__BUNITE_DEBUG__) {
|
|
42
|
+
console.warn("[bunite] surface call failed", err);
|
|
43
|
+
}
|
|
44
|
+
return undefined;
|
|
45
|
+
});
|
|
34
46
|
}
|
|
35
47
|
|
|
36
48
|
/** Like `callSurface` but never swallows — automation surface API needs to
|
|
@@ -92,7 +104,7 @@ class OverlaySyncController {
|
|
|
92
104
|
x: Math.round(r.x * dpr),
|
|
93
105
|
y: Math.round(r.y * dpr),
|
|
94
106
|
width: Math.round(r.width * dpr),
|
|
95
|
-
height: Math.round(r.height * dpr)
|
|
107
|
+
height: Math.round(r.height * dpr),
|
|
96
108
|
};
|
|
97
109
|
|
|
98
110
|
// Always check for position changes (not caught by ResizeObserver)
|
|
@@ -130,11 +142,6 @@ class BuniteWebviewElement extends HTMLElement {
|
|
|
130
142
|
private _unsubNavigate: (() => void) | null = null;
|
|
131
143
|
private _activeStreams: Array<{ cancel?: () => void }> = [];
|
|
132
144
|
|
|
133
|
-
constructor() {
|
|
134
|
-
super();
|
|
135
|
-
// NOTE: Custom element spec forbids setting attributes in constructor.
|
|
136
|
-
}
|
|
137
|
-
|
|
138
145
|
connectedCallback() {
|
|
139
146
|
this._aborted = false;
|
|
140
147
|
this._syncHidden = false;
|
|
@@ -156,9 +163,13 @@ class BuniteWebviewElement extends HTMLElement {
|
|
|
156
163
|
try {
|
|
157
164
|
for await (const event of dlStream) {
|
|
158
165
|
if (ctrl.signal.aborted) break;
|
|
159
|
-
this.dispatchEvent(
|
|
166
|
+
this.dispatchEvent(
|
|
167
|
+
new CustomEvent<DownloadEvent>("download-event", { detail: event }),
|
|
168
|
+
);
|
|
160
169
|
}
|
|
161
|
-
} catch {
|
|
170
|
+
} catch {
|
|
171
|
+
/* stream torn down */
|
|
172
|
+
}
|
|
162
173
|
})();
|
|
163
174
|
for await (const event of surfStream) {
|
|
164
175
|
if (ctrl.signal.aborted) break;
|
|
@@ -179,7 +190,11 @@ class BuniteWebviewElement extends HTMLElement {
|
|
|
179
190
|
if (pending) {
|
|
180
191
|
// Await this exact init attempt; if it rejects, bail (init failed —
|
|
181
192
|
// we'd otherwise spin forever waiting for a surfaceId that never lands).
|
|
182
|
-
try {
|
|
193
|
+
try {
|
|
194
|
+
await pending;
|
|
195
|
+
} catch {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
183
198
|
// After resolve, _surfaceId may still be null if disconnect raced —
|
|
184
199
|
// the next loop iteration checks signal.aborted to exit cleanly.
|
|
185
200
|
} else {
|
|
@@ -223,7 +238,9 @@ class BuniteWebviewElement extends HTMLElement {
|
|
|
223
238
|
// Cancel pending stream iterators so the `for await` actually unblocks —
|
|
224
239
|
// AbortController alone only takes effect at the next received chunk.
|
|
225
240
|
for (const stream of this._activeStreams) {
|
|
226
|
-
try {
|
|
241
|
+
try {
|
|
242
|
+
stream.cancel?.();
|
|
243
|
+
} catch {}
|
|
227
244
|
}
|
|
228
245
|
this._activeStreams = [];
|
|
229
246
|
this._layoutObserver?.disconnect();
|
|
@@ -237,7 +254,9 @@ class BuniteWebviewElement extends HTMLElement {
|
|
|
237
254
|
void callSurface((s) => s.remove({ surfaceId: id }));
|
|
238
255
|
} else if (this._initPromise) {
|
|
239
256
|
this._initPromise
|
|
240
|
-
.then((r) => {
|
|
257
|
+
.then((r) => {
|
|
258
|
+
void callSurface((s) => s.remove({ surfaceId: r.surfaceId }));
|
|
259
|
+
})
|
|
241
260
|
.catch(() => {});
|
|
242
261
|
}
|
|
243
262
|
this._initPromise = null;
|
|
@@ -286,11 +305,24 @@ class BuniteWebviewElement extends HTMLElement {
|
|
|
286
305
|
const sid = this._surfaceId;
|
|
287
306
|
if (sid == null) {
|
|
288
307
|
return {
|
|
289
|
-
evaluate: false,
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
308
|
+
evaluate: false,
|
|
309
|
+
crossOriginEval: false,
|
|
310
|
+
surfaceEvents: false,
|
|
311
|
+
nativeInputTrusted: false,
|
|
312
|
+
click: false,
|
|
313
|
+
type: false,
|
|
314
|
+
press: false,
|
|
315
|
+
scroll: false,
|
|
316
|
+
mouse: false,
|
|
317
|
+
dialogs: false,
|
|
318
|
+
console: false,
|
|
319
|
+
screenshot: false,
|
|
320
|
+
accessibilitySnapshot: false,
|
|
321
|
+
getBoundingRect: false,
|
|
322
|
+
frames: false,
|
|
323
|
+
downloads: false,
|
|
324
|
+
popups: false,
|
|
325
|
+
resolveAndClick: false,
|
|
294
326
|
};
|
|
295
327
|
}
|
|
296
328
|
return callSurfaceTyped((s) => s.capabilities({ surfaceId: sid }));
|
|
@@ -298,7 +330,8 @@ class BuniteWebviewElement extends HTMLElement {
|
|
|
298
330
|
|
|
299
331
|
// Automation input — `send*` prefix avoids clashing with HTMLElement.click() / .scroll().
|
|
300
332
|
async sendClick(args: {
|
|
301
|
-
x: number;
|
|
333
|
+
x: number;
|
|
334
|
+
y: number;
|
|
302
335
|
button?: "left" | "middle" | "right";
|
|
303
336
|
clickCount?: number;
|
|
304
337
|
modifiers?: Array<"alt" | "ctrl" | "meta" | "shift">;
|
|
@@ -317,7 +350,7 @@ class BuniteWebviewElement extends HTMLElement {
|
|
|
317
350
|
async sendPress(
|
|
318
351
|
key: string,
|
|
319
352
|
modifiers?: Array<"alt" | "ctrl" | "meta" | "shift">,
|
|
320
|
-
action?: "down" | "up" | "both"
|
|
353
|
+
action?: "down" | "up" | "both",
|
|
321
354
|
): Promise<void> {
|
|
322
355
|
const sid = this._surfaceId;
|
|
323
356
|
if (sid == null) return;
|
|
@@ -325,7 +358,10 @@ class BuniteWebviewElement extends HTMLElement {
|
|
|
325
358
|
}
|
|
326
359
|
|
|
327
360
|
async sendScroll(args: {
|
|
328
|
-
dx: number;
|
|
361
|
+
dx: number;
|
|
362
|
+
dy: number;
|
|
363
|
+
x?: number;
|
|
364
|
+
y?: number;
|
|
329
365
|
modifiers?: Array<"alt" | "ctrl" | "meta" | "shift">;
|
|
330
366
|
}): Promise<void> {
|
|
331
367
|
const sid = this._surfaceId;
|
|
@@ -335,7 +371,8 @@ class BuniteWebviewElement extends HTMLElement {
|
|
|
335
371
|
|
|
336
372
|
async sendMouse(args: {
|
|
337
373
|
action: "move" | "down" | "up";
|
|
338
|
-
x: number;
|
|
374
|
+
x: number;
|
|
375
|
+
y: number;
|
|
339
376
|
button?: "left" | "middle" | "right";
|
|
340
377
|
modifiers?: Array<"alt" | "ctrl" | "meta" | "shift">;
|
|
341
378
|
}): Promise<void> {
|
|
@@ -362,7 +399,10 @@ class BuniteWebviewElement extends HTMLElement {
|
|
|
362
399
|
return callSurfaceTyped((s) => s.waitForSelector({ surfaceId: sid, selector, timeoutMs }));
|
|
363
400
|
}
|
|
364
401
|
|
|
365
|
-
async waitForFunction(
|
|
402
|
+
async waitForFunction(
|
|
403
|
+
expression: string,
|
|
404
|
+
opts?: { timeoutMs?: number; pollIntervalMs?: number },
|
|
405
|
+
): Promise<WaitResult> {
|
|
366
406
|
const sid = this._surfaceId;
|
|
367
407
|
if (sid == null) return { ok: false, code: "runtime_error", message: "surface not ready" };
|
|
368
408
|
return callSurfaceTyped((s) => s.waitForFunction({ surfaceId: sid, expression, ...opts }));
|
|
@@ -371,7 +411,10 @@ class BuniteWebviewElement extends HTMLElement {
|
|
|
371
411
|
async getConsoleBuffer(opts?: { clear?: boolean }): Promise<ConsoleEntry[]> {
|
|
372
412
|
const sid = this._surfaceId;
|
|
373
413
|
if (sid == null) return [];
|
|
374
|
-
return (
|
|
414
|
+
return (
|
|
415
|
+
(await callSurfaceTyped((s) => s.getConsoleBuffer({ surfaceId: sid, clear: opts?.clear }))) ??
|
|
416
|
+
[]
|
|
417
|
+
);
|
|
375
418
|
}
|
|
376
419
|
|
|
377
420
|
async getNavigationState(): Promise<NavigationState> {
|
|
@@ -380,16 +423,25 @@ class BuniteWebviewElement extends HTMLElement {
|
|
|
380
423
|
return callSurfaceTyped((s) => s.getNavigationState({ surfaceId: sid }));
|
|
381
424
|
}
|
|
382
425
|
|
|
383
|
-
async accessibilitySnapshot(opts?: {
|
|
426
|
+
async accessibilitySnapshot(opts?: {
|
|
427
|
+
interestingOnly?: boolean;
|
|
428
|
+
}): Promise<AccessibilitySnapshotResult> {
|
|
384
429
|
const sid = this._surfaceId;
|
|
385
430
|
if (sid == null) return { ok: false, code: "not_supported", message: "surface not ready" };
|
|
386
|
-
return callSurfaceTyped((s) =>
|
|
431
|
+
return callSurfaceTyped((s) =>
|
|
432
|
+
s.accessibilitySnapshot({ surfaceId: sid, interestingOnly: opts?.interestingOnly }),
|
|
433
|
+
);
|
|
387
434
|
}
|
|
388
435
|
|
|
389
|
-
async getBoundingRect(
|
|
436
|
+
async getBoundingRect(
|
|
437
|
+
selector: string,
|
|
438
|
+
opts?: { frameId?: string },
|
|
439
|
+
): Promise<BoundingRectResult> {
|
|
390
440
|
const sid = this._surfaceId;
|
|
391
441
|
if (sid == null) return { ok: false, code: "runtime_error", message: "surface not ready" };
|
|
392
|
-
return callSurfaceTyped((s) =>
|
|
442
|
+
return callSurfaceTyped((s) =>
|
|
443
|
+
s.getBoundingRect({ surfaceId: sid, selector, frameId: opts?.frameId }),
|
|
444
|
+
);
|
|
393
445
|
}
|
|
394
446
|
|
|
395
447
|
async listFrames(): Promise<ListFramesResult> {
|
|
@@ -405,7 +457,7 @@ class BuniteWebviewElement extends HTMLElement {
|
|
|
405
457
|
button?: "left" | "middle" | "right";
|
|
406
458
|
clickCount?: number;
|
|
407
459
|
modifiers?: Array<"alt" | "ctrl" | "meta" | "shift">;
|
|
408
|
-
}
|
|
460
|
+
},
|
|
409
461
|
): Promise<ResolveAndClickResult> {
|
|
410
462
|
const sid = this._surfaceId;
|
|
411
463
|
if (sid == null) return { ok: false, code: "runtime_error", message: "surface not ready" };
|
|
@@ -421,7 +473,9 @@ class BuniteWebviewElement extends HTMLElement {
|
|
|
421
473
|
async waitForDownload(opts?: { timeoutMs?: number }): Promise<WaitForDownloadResult> {
|
|
422
474
|
const sid = this._surfaceId;
|
|
423
475
|
if (sid == null) return { ok: false, code: "not_supported", message: "surface not ready" };
|
|
424
|
-
return callSurfaceTyped((s) =>
|
|
476
|
+
return callSurfaceTyped((s) =>
|
|
477
|
+
s.waitForDownload({ surfaceId: sid, timeoutMs: opts?.timeoutMs }),
|
|
478
|
+
);
|
|
425
479
|
}
|
|
426
480
|
|
|
427
481
|
async dismissPopup(newSurfaceId: number): Promise<void> {
|
|
@@ -432,7 +486,10 @@ class BuniteWebviewElement extends HTMLElement {
|
|
|
432
486
|
return callSurfaceTyped((s) => s.extendPopupTimeout({ newSurfaceId, gracePeriodMs }));
|
|
433
487
|
}
|
|
434
488
|
|
|
435
|
-
async screenshot(args?: {
|
|
489
|
+
async screenshot(args?: {
|
|
490
|
+
format?: "png" | "jpeg";
|
|
491
|
+
quality?: number;
|
|
492
|
+
}): Promise<ScreenshotResult> {
|
|
436
493
|
const sid = this._surfaceId;
|
|
437
494
|
if (sid == null) return { ok: false, code: "not_supported", message: "surface not ready" };
|
|
438
495
|
return callSurfaceTyped((s) => s.screenshot({ surfaceId: sid, ...args }));
|
|
@@ -461,9 +518,15 @@ class BuniteWebviewElement extends HTMLElement {
|
|
|
461
518
|
this._syncHidden = false;
|
|
462
519
|
this._applySurfaceHidden();
|
|
463
520
|
}
|
|
464
|
-
void callSurface((s) =>
|
|
465
|
-
|
|
466
|
-
|
|
521
|
+
void callSurface((s) =>
|
|
522
|
+
s.resize({
|
|
523
|
+
surfaceId: sid,
|
|
524
|
+
x: rect.x,
|
|
525
|
+
y: rect.y,
|
|
526
|
+
w: rect.width,
|
|
527
|
+
h: rect.height,
|
|
528
|
+
}),
|
|
529
|
+
);
|
|
467
530
|
});
|
|
468
531
|
this._syncCtrl.start();
|
|
469
532
|
}
|
|
@@ -493,34 +556,38 @@ class BuniteWebviewElement extends HTMLElement {
|
|
|
493
556
|
return { surfaceId: adoptId };
|
|
494
557
|
}) as Promise<SurfaceInitResponse>;
|
|
495
558
|
this._initPromise = initPromise;
|
|
496
|
-
initPromise
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
559
|
+
initPromise
|
|
560
|
+
.then((response) => {
|
|
561
|
+
if (this._initPromise !== initPromise) return;
|
|
562
|
+
if (this._aborted) {
|
|
563
|
+
void callSurface((s) => s.remove({ surfaceId: response.surfaceId }));
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
this._surfaceId = response.surfaceId;
|
|
567
|
+
this._setupSyncCtrl();
|
|
568
|
+
})
|
|
569
|
+
.catch((err) => {
|
|
570
|
+
if ((globalThis as { __BUNITE_DEBUG__?: boolean }).__BUNITE_DEBUG__) {
|
|
571
|
+
console.warn("[bunite] adopt-popup-id init failed", err);
|
|
572
|
+
}
|
|
573
|
+
this._initPromise = null;
|
|
574
|
+
});
|
|
510
575
|
return;
|
|
511
576
|
}
|
|
512
577
|
|
|
513
578
|
const src = this._pendingSrc || this.getAttribute("src") || "";
|
|
514
579
|
this._pendingSrc = null;
|
|
515
580
|
|
|
516
|
-
const initPromise = getSurfaceCap().then((s) =>
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
581
|
+
const initPromise = getSurfaceCap().then((s) =>
|
|
582
|
+
s.init({
|
|
583
|
+
src,
|
|
584
|
+
x: Math.round(r.x * dpr),
|
|
585
|
+
y: Math.round(r.y * dpr),
|
|
586
|
+
width: Math.round(r.width * dpr),
|
|
587
|
+
height: Math.round(r.height * dpr),
|
|
588
|
+
hidden: this._userHidden,
|
|
589
|
+
}),
|
|
590
|
+
) as Promise<SurfaceInitResponse>;
|
|
524
591
|
this._initPromise = initPromise;
|
|
525
592
|
|
|
526
593
|
initPromise
|
|
@@ -560,14 +627,24 @@ class BuniteWebviewElement extends HTMLElement {
|
|
|
560
627
|
if (typeof customElements !== "undefined") {
|
|
561
628
|
customElements.define("bunite-webview", BuniteWebviewElement);
|
|
562
629
|
|
|
563
|
-
const raiseAll = () => {
|
|
630
|
+
const raiseAll = () => {
|
|
631
|
+
void callSurface((s) => s.bringAllVisiblesToFront());
|
|
632
|
+
};
|
|
564
633
|
document.addEventListener("pointerdown", raiseAll, true);
|
|
565
634
|
|
|
566
|
-
document.addEventListener(
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
635
|
+
document.addEventListener(
|
|
636
|
+
"dragstart",
|
|
637
|
+
() => {
|
|
638
|
+
void callSurface((s) => s.setAllPassthrough({ passthrough: true }));
|
|
639
|
+
},
|
|
640
|
+
true,
|
|
641
|
+
);
|
|
642
|
+
document.addEventListener(
|
|
643
|
+
"dragend",
|
|
644
|
+
() => {
|
|
645
|
+
void callSurface((s) => s.setAllPassthrough({ passthrough: false }));
|
|
646
|
+
raiseAll();
|
|
647
|
+
},
|
|
648
|
+
true,
|
|
649
|
+
);
|
|
573
650
|
}
|