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.
@@ -1,32 +1,47 @@
1
1
  import { ptr } from "bun:ffi";
2
- import { buildViewPreloadScript } from "../preloadBundle";
3
- import { log } from "../log";
4
- import { buniteEventEmitter } from "../events/eventEmitter";
2
+ import { randomBytes } from "node:crypto";
3
+ import type {
4
+ AccessibilitySnapshotResult,
5
+ AxNode,
6
+ EvaluateResult,
7
+ ListFramesResult,
8
+ Modifier,
9
+ ResolveAndClickArgs,
10
+ ResolveAndClickResult,
11
+ ScreenshotResult,
12
+ SurfaceCapabilities,
13
+ } from "../../rpc/framework";
5
14
  import {
15
+ type BytesPipe,
16
+ type Connection,
6
17
  createConnection,
7
18
  createFrameTransport,
8
- type Connection,
9
- type BytesPipe,
10
19
  } from "../../rpc/index";
11
- import type {
12
- EvaluateResult, SurfaceCapabilities, ScreenshotResult, Modifier,
13
- AccessibilitySnapshotResult, AxNode, ListFramesResult,
14
- ResolveAndClickArgs, ResolveAndClickResult,
15
- } from "../../rpc/framework";
16
- import { encodeModifiers, resolveKey } from "./inputDispatch";
17
20
  import { createEncryptedPipe } from "../encryptedPipe";
21
+ import { buniteEventEmitter } from "../events/eventEmitter";
22
+ import { log } from "../log";
18
23
  import {
19
- ensureNativeRuntime, getNativeLibrary, toCString, waitForViewReady, cancelWaitForViewReady,
20
- setEvaluateResultHandler, type NativeEvaluateResult,
21
- setScreenshotResultHandler, type NativeScreenshotResult,
22
- setAccessibilityResultHandler, type NativeAccessibilityResult,
23
- setListFramesResultHandler, type NativeListFramesResult,
24
- setResolveAndClickResultHandler, type NativeResolveAndClickResult,
24
+ cancelWaitForViewReady,
25
+ ensureNativeRuntime,
26
+ getNativeLibrary,
27
+ type NativeAccessibilityResult,
28
+ type NativeEvaluateResult,
29
+ type NativeListFramesResult,
30
+ type NativeResolveAndClickResult,
31
+ type NativeScreenshotResult,
32
+ setAccessibilityResultHandler,
33
+ setEvaluateResultHandler,
34
+ setListFramesResultHandler,
35
+ setResolveAndClickResultHandler,
36
+ setScreenshotResultHandler,
37
+ toCString,
38
+ waitForViewReady,
25
39
  } from "../native";
26
- import { attachBrowserViewRegistry, getRpcPort } from "./Socket";
27
- import { getAppRuntimeOrThrow } from "./App";
28
- import { randomBytes } from "node:crypto";
29
40
  import { resolveDefaultAppResRoot } from "../paths";
41
+ import { buildViewPreloadScript } from "../preloadBundle";
42
+ import { getAppRuntimeOrThrow } from "./App";
43
+ import { encodeModifiers, resolveKey } from "./inputDispatch";
44
+ import { attachBrowserViewRegistry, getRpcPort } from "./Socket";
30
45
  import { removeSurfacesForHostView } from "./SurfaceRegistry";
31
46
 
32
47
  const BrowserViewMap: Record<number, BrowserView> = {};
@@ -40,7 +55,10 @@ type EvaluatePending = { viewId: number; resolve: (result: EvaluateResult) => vo
40
55
  let nextEvaluateRequestId = 1;
41
56
  const evaluateResolvers = new Map<number, EvaluatePending>();
42
57
 
43
- function registerEvaluateRequest(viewId: number, resolve: (result: EvaluateResult) => void): number {
58
+ function registerEvaluateRequest(
59
+ viewId: number,
60
+ resolve: (result: EvaluateResult) => void,
61
+ ): number {
44
62
  const id = nextEvaluateRequestId++;
45
63
  evaluateResolvers.set(id, { viewId, resolve });
46
64
  return id;
@@ -61,7 +79,10 @@ type ScreenshotPending = { viewId: number; resolve: (result: ScreenshotResult) =
61
79
  let nextScreenshotRequestId = 1;
62
80
  const screenshotResolvers = new Map<number, ScreenshotPending>();
63
81
 
64
- function registerScreenshotRequest(viewId: number, resolve: (result: ScreenshotResult) => void): number {
82
+ function registerScreenshotRequest(
83
+ viewId: number,
84
+ resolve: (result: ScreenshotResult) => void,
85
+ ): number {
65
86
  const id = nextScreenshotRequestId++;
66
87
  screenshotResolvers.set(id, { viewId, resolve });
67
88
  return id;
@@ -76,11 +97,19 @@ function rejectScreenshotsForView(viewId: number) {
76
97
  }
77
98
  }
78
99
 
79
- type AxPending = { viewId: number; resolve: (result: AccessibilitySnapshotResult) => void; interestingOnly: boolean };
100
+ type AxPending = {
101
+ viewId: number;
102
+ resolve: (result: AccessibilitySnapshotResult) => void;
103
+ interestingOnly: boolean;
104
+ };
80
105
  let nextAxRequestId = 1;
81
106
  const axResolvers = new Map<number, AxPending>();
82
107
 
83
- function registerAxRequest(viewId: number, resolve: (result: AccessibilitySnapshotResult) => void, interestingOnly: boolean): number {
108
+ function registerAxRequest(
109
+ viewId: number,
110
+ resolve: (result: AccessibilitySnapshotResult) => void,
111
+ interestingOnly: boolean,
112
+ ): number {
84
113
  const id = nextAxRequestId++;
85
114
  axResolvers.set(id, { viewId, resolve, interestingOnly });
86
115
  return id;
@@ -124,7 +153,7 @@ function convertAxTree(cdpResult: { nodes?: any[] } | undefined, interestingOnly
124
153
  };
125
154
  const build = (n: any): AxNode => {
126
155
  const id = String(n?.nodeId ?? "");
127
- if (id && seen.has(id)) return { nodeId: id, role: "", name: "" }; // cycle guard
156
+ if (id && seen.has(id)) return { nodeId: id, role: "", name: "" }; // cycle guard
128
157
  if (id) seen.add(id);
129
158
  const props = new Map<string, unknown>();
130
159
  if (Array.isArray(n?.properties)) {
@@ -139,15 +168,24 @@ function convertAxTree(cdpResult: { nodes?: any[] } | undefined, interestingOnly
139
168
  };
140
169
  if (n?.value?.value !== undefined) out.value = String(n.value.value);
141
170
  if (n?.description?.value !== undefined) out.description = String(n.description.value);
142
- const level = props.get("level"); if (typeof level === "number") out.level = level;
143
- const checked = props.get("checked"); if (checked === true || checked === false || checked === "mixed") out.checked = checked;
144
- const pressed = props.get("pressed"); if (pressed === true || pressed === false || pressed === "mixed") out.pressed = pressed;
145
- const expanded = props.get("expanded"); if (typeof expanded === "boolean") out.expanded = expanded;
146
- const disabled = props.get("disabled"); if (typeof disabled === "boolean") out.disabled = disabled;
147
- const focused = props.get("focused"); if (typeof focused === "boolean") out.focused = focused;
148
- const invalid = props.get("invalid"); if (typeof invalid === "boolean") out.invalid = invalid;
149
- const required = props.get("required"); if (typeof required === "boolean") out.required = required;
150
- const selected = props.get("selected"); if (typeof selected === "boolean") out.selected = selected;
171
+ const level = props.get("level");
172
+ if (typeof level === "number") out.level = level;
173
+ const checked = props.get("checked");
174
+ if (checked === true || checked === false || checked === "mixed") out.checked = checked;
175
+ const pressed = props.get("pressed");
176
+ if (pressed === true || pressed === false || pressed === "mixed") out.pressed = pressed;
177
+ const expanded = props.get("expanded");
178
+ if (typeof expanded === "boolean") out.expanded = expanded;
179
+ const disabled = props.get("disabled");
180
+ if (typeof disabled === "boolean") out.disabled = disabled;
181
+ const focused = props.get("focused");
182
+ if (typeof focused === "boolean") out.focused = focused;
183
+ const invalid = props.get("invalid");
184
+ if (typeof invalid === "boolean") out.invalid = invalid;
185
+ const required = props.get("required");
186
+ if (typeof required === "boolean") out.required = required;
187
+ const selected = props.get("selected");
188
+ if (typeof selected === "boolean") out.selected = selected;
151
189
  const kids = interestingDescendants(n).map(build);
152
190
  if (kids.length > 0) out.children = kids;
153
191
  return out;
@@ -158,7 +196,10 @@ function convertAxTree(cdpResult: { nodes?: any[] } | undefined, interestingOnly
158
196
  type FramesPending = { viewId: number; resolve: (result: ListFramesResult) => void };
159
197
  let nextFramesRequestId = 1;
160
198
  const framesResolvers = new Map<number, FramesPending>();
161
- function registerFramesRequest(viewId: number, resolve: (result: ListFramesResult) => void): number {
199
+ function registerFramesRequest(
200
+ viewId: number,
201
+ resolve: (result: ListFramesResult) => void,
202
+ ): number {
162
203
  const id = nextFramesRequestId++;
163
204
  framesResolvers.set(id, { viewId, resolve });
164
205
  return id;
@@ -175,7 +216,10 @@ function rejectFramesForView(viewId: number) {
175
216
  type ResolveAndClickPending = { viewId: number; resolve: (result: ResolveAndClickResult) => void };
176
217
  let nextResolveAndClickRequestId = 1;
177
218
  const resolveAndClickResolvers = new Map<number, ResolveAndClickPending>();
178
- function registerResolveAndClickRequest(viewId: number, resolve: (result: ResolveAndClickResult) => void): number {
219
+ function registerResolveAndClickRequest(
220
+ viewId: number,
221
+ resolve: (result: ResolveAndClickResult) => void,
222
+ ): number {
179
223
  const id = nextResolveAndClickRequestId++;
180
224
  resolveAndClickResolvers.set(id, { viewId, resolve });
181
225
  return id;
@@ -189,12 +233,26 @@ function rejectResolveAndClickForView(viewId: number) {
189
233
  }
190
234
  }
191
235
 
192
- function flattenFrameTree(raw: any): { frameId: string; parentFrameId: string | null; origin: string; url: string; name?: string }[] {
193
- const out: { frameId: string; parentFrameId: string | null; origin: string; url: string; name?: string }[] = [];
236
+ function flattenFrameTree(
237
+ raw: any,
238
+ ): { frameId: string; parentFrameId: string | null; origin: string; url: string; name?: string }[] {
239
+ const out: {
240
+ frameId: string;
241
+ parentFrameId: string | null;
242
+ origin: string;
243
+ url: string;
244
+ name?: string;
245
+ }[] = [];
194
246
  const walk = (node: any, parent: string | null) => {
195
247
  const f = node?.frame;
196
248
  if (!f) return;
197
- const entry: { frameId: string; parentFrameId: string | null; origin: string; url: string; name?: string } = {
249
+ const entry: {
250
+ frameId: string;
251
+ parentFrameId: string | null;
252
+ origin: string;
253
+ url: string;
254
+ name?: string;
255
+ } = {
198
256
  frameId: String(f.id ?? ""),
199
257
  parentFrameId: parent,
200
258
  origin: String(f.securityOrigin ?? ""),
@@ -218,7 +276,11 @@ setListFramesResultHandler((viewId, raw: NativeListFramesResult) => {
218
276
  const frames = flattenFrameTree(raw.raw);
219
277
  entry.resolve({ ok: true, frames });
220
278
  } catch (e) {
221
- entry.resolve({ ok: false, code: "runtime_error", message: `frame tree flatten failed: ${(e as Error).message}` });
279
+ entry.resolve({
280
+ ok: false,
281
+ code: "runtime_error",
282
+ message: `frame tree flatten failed: ${(e as Error).message}`,
283
+ });
222
284
  }
223
285
  } else {
224
286
  entry.resolve({
@@ -234,8 +296,15 @@ setAccessibilityResultHandler((viewId, raw: NativeAccessibilityResult) => {
234
296
  if (!entry || entry.viewId !== viewId) return;
235
297
  axResolvers.delete(raw.requestId);
236
298
  if (raw.ok && raw.tree) {
237
- try { entry.resolve({ ok: true, tree: convertAxTree(raw.tree as any, entry.interestingOnly) }); }
238
- catch (e) { entry.resolve({ ok: false, code: "runtime_error", message: `ax tree convert failed: ${(e as Error).message}` }); }
299
+ try {
300
+ entry.resolve({ ok: true, tree: convertAxTree(raw.tree as any, entry.interestingOnly) });
301
+ } catch (e) {
302
+ entry.resolve({
303
+ ok: false,
304
+ code: "runtime_error",
305
+ message: `ax tree convert failed: ${(e as Error).message}`,
306
+ });
307
+ }
239
308
  } else {
240
309
  entry.resolve({
241
310
  ok: false,
@@ -259,9 +328,18 @@ setScreenshotResultHandler((viewId, raw: NativeScreenshotResult) => {
259
328
  screenshotResolvers.delete(raw.requestId);
260
329
  if (raw.ok && raw.dataBase64 && raw.format && raw.mime) {
261
330
  try {
262
- entry.resolve({ ok: true, data: decodeBase64(raw.dataBase64), mime: raw.mime, format: raw.format });
331
+ entry.resolve({
332
+ ok: true,
333
+ data: decodeBase64(raw.dataBase64),
334
+ mime: raw.mime,
335
+ format: raw.format,
336
+ });
263
337
  } catch (e) {
264
- entry.resolve({ ok: false, code: "runtime_error", message: `base64 decode failed: ${(e as Error).message}` });
338
+ entry.resolve({
339
+ ok: false,
340
+ code: "runtime_error",
341
+ message: `base64 decode failed: ${(e as Error).message}`,
342
+ });
265
343
  }
266
344
  } else {
267
345
  entry.resolve({
@@ -279,7 +357,12 @@ setResolveAndClickResultHandler((viewId, raw: NativeResolveAndClickResult) => {
279
357
  if (raw.ok && raw.rect) {
280
358
  entry.resolve({ ok: true, rect: raw.rect, isTrustedEvent: !!raw.isTrustedEvent });
281
359
  } else {
282
- type FailCode = "not_found" | "not_visible" | "runtime_error" | "cross_origin" | "not_supported";
360
+ type FailCode =
361
+ | "not_found"
362
+ | "not_visible"
363
+ | "runtime_error"
364
+ | "cross_origin"
365
+ | "not_supported";
283
366
  const code = (raw.code as FailCode | undefined) ?? "runtime_error";
284
367
  entry.resolve({ ok: false, code, message: raw.message ?? "resolveAndClick failed" });
285
368
  }
@@ -288,13 +371,17 @@ setResolveAndClickResultHandler((viewId, raw: NativeResolveAndClickResult) => {
288
371
  setEvaluateResultHandler((viewId, raw: NativeEvaluateResult) => {
289
372
  const entry = evaluateResolvers.get(raw.requestId);
290
373
  if (!entry) return;
291
- if (entry.viewId !== viewId) return; // foreign event — ignore
374
+ if (entry.viewId !== viewId) return; // foreign event — ignore
292
375
  evaluateResolvers.delete(raw.requestId);
293
376
  if (raw.ok && raw.value !== undefined) {
294
377
  try {
295
378
  entry.resolve({ ok: true, value: JSON.parse(raw.value) });
296
379
  } catch (e) {
297
- entry.resolve({ ok: false, code: "runtime_error", message: `result JSON parse failed: ${(e as Error).message}` });
380
+ entry.resolve({
381
+ ok: false,
382
+ code: "runtime_error",
383
+ message: `result JSON parse failed: ${(e as Error).message}`,
384
+ });
298
385
  }
299
386
  } else {
300
387
  entry.resolve({
@@ -306,26 +393,26 @@ setEvaluateResultHandler((viewId, raw: NativeEvaluateResult) => {
306
393
  });
307
394
 
308
395
  // Bit positions match the native enum in `ffi_exports.h` (BuniteCapBit).
309
- const CAP_EVALUATE = 1 << 0;
310
- const CAP_CROSS_ORIGIN_EVAL = 1 << 1;
311
- const CAP_SURFACE_EVENTS = 1 << 2;
396
+ const CAP_EVALUATE = 1 << 0;
397
+ const CAP_CROSS_ORIGIN_EVAL = 1 << 1;
398
+ const CAP_SURFACE_EVENTS = 1 << 2;
312
399
  const CAP_NATIVE_INPUT_TRUSTED = 1 << 3;
313
- const CAP_CLICK = 1 << 4;
314
- const CAP_TYPE = 1 << 5;
315
- const CAP_PRESS = 1 << 6;
316
- const CAP_SCROLL = 1 << 7;
317
- const CAP_SCREENSHOT = 1 << 8;
318
- const CAP_FORMAT_PNG = 1 << 9;
319
- const CAP_FORMAT_JPEG = 1 << 10;
320
- const CAP_MOUSE = 1 << 11;
321
- const CAP_DIALOGS = 1 << 12;
322
- const CAP_CONSOLE = 1 << 13;
323
- const CAP_AX = 1 << 15;
324
- const CAP_BOUNDING_RECT = 1 << 16;
325
- const CAP_FRAMES = 1 << 17;
326
- const CAP_DOWNLOADS = 1 << 18;
327
- const CAP_POPUPS = 1 << 19;
328
- const CAP_RESOLVE_AND_CLICK = 1 << 20;
400
+ const CAP_CLICK = 1 << 4;
401
+ const CAP_TYPE = 1 << 5;
402
+ const CAP_PRESS = 1 << 6;
403
+ const CAP_SCROLL = 1 << 7;
404
+ const CAP_SCREENSHOT = 1 << 8;
405
+ const CAP_FORMAT_PNG = 1 << 9;
406
+ const CAP_FORMAT_JPEG = 1 << 10;
407
+ const CAP_MOUSE = 1 << 11;
408
+ const CAP_DIALOGS = 1 << 12;
409
+ const CAP_CONSOLE = 1 << 13;
410
+ const CAP_AX = 1 << 15;
411
+ const CAP_BOUNDING_RECT = 1 << 16;
412
+ const CAP_FRAMES = 1 << 17;
413
+ const CAP_DOWNLOADS = 1 << 18;
414
+ const CAP_POPUPS = 1 << 19;
415
+ const CAP_RESOLVE_AND_CLICK = 1 << 20;
329
416
 
330
417
  function decodeCapabilityBits(bits: number): SurfaceCapabilities {
331
418
  const formats: ("png" | "jpeg")[] = [];
@@ -386,7 +473,7 @@ const defaultOptions: BrowserViewOptions = {
386
473
  windowId: 0,
387
474
  autoResize: true,
388
475
  navigationRules: null,
389
- sandbox: false
476
+ sandbox: false,
390
477
  };
391
478
 
392
479
  export class BrowserView {
@@ -465,7 +552,7 @@ export class BrowserView {
465
552
  appresRoot: this.appresRoot,
466
553
  webviewId: this.id,
467
554
  rpcSocketPort: getRpcPort(),
468
- secretKey: this.secretKey
555
+ secretKey: this.secretKey,
469
556
  });
470
557
 
471
558
  BrowserViewMap[this.id] = this;
@@ -474,8 +561,12 @@ export class BrowserView {
474
561
  // Native popup mint already created the view; bind to host window + bounds.
475
562
  const lib = getNativeLibrary();
476
563
  lib?.symbols.bunite_view_popup_accept(
477
- this.id, this.windowId,
478
- this.frame.x, this.frame.y, this.frame.width, this.frame.height,
564
+ this.id,
565
+ this.windowId,
566
+ this.frame.x,
567
+ this.frame.y,
568
+ this.frame.width,
569
+ this.frame.height,
479
570
  );
480
571
  this.nativeAttached = true;
481
572
  } else {
@@ -494,7 +585,7 @@ export class BrowserView {
494
585
  this.frame.height,
495
586
  this.autoResize,
496
587
  this.sandbox,
497
- toCString(this.preloadOrigins ? JSON.stringify(this.preloadOrigins) : "")
588
+ toCString(this.preloadOrigins ? JSON.stringify(this.preloadOrigins) : ""),
498
589
  ) ?? false;
499
590
  }
500
591
 
@@ -514,8 +605,11 @@ export class BrowserView {
514
605
  return Promise.race([
515
606
  this._readyPromise,
516
607
  new Promise<never>((_, reject) =>
517
- setTimeout(() => reject(new Error(`Browser creation timed out for view ${this.id}`)), timeoutMs)
518
- )
608
+ setTimeout(
609
+ () => reject(new Error(`Browser creation timed out for view ${this.id}`)),
610
+ timeoutMs,
611
+ ),
612
+ ),
519
613
  ]);
520
614
  }
521
615
 
@@ -531,12 +625,20 @@ export class BrowserView {
531
625
  this.connectionGeneration += 1;
532
626
  const myGen = this.connectionGeneration;
533
627
  if (this.connection) {
534
- try { (this.connection as { transport?: { close?(): void } }).transport?.close?.(); } catch { /* swallow */ }
628
+ try {
629
+ (this.connection as { transport?: { close?(): void } }).transport?.close?.();
630
+ } catch {
631
+ /* swallow */
632
+ }
535
633
  this.connection = null;
536
634
  }
537
635
  const encPipe = await createEncryptedPipe(pipe, this.secretKey);
538
636
  if (myGen !== this.connectionGeneration) {
539
- try { encPipe.close(); } catch { /* swallow */ }
637
+ try {
638
+ encPipe.close();
639
+ } catch {
640
+ /* swallow */
641
+ }
540
642
  return;
541
643
  }
542
644
  const runtime = getAppRuntimeOrThrow().createViewRuntime(this.id);
@@ -561,7 +663,11 @@ export class BrowserView {
561
663
  detachNewConnection(): void {
562
664
  this.connectionGeneration += 1;
563
665
  if (this.connection) {
564
- try { (this.connection as { transport?: { close?(): void } }).transport?.close?.(); } catch { /* swallow */ }
666
+ try {
667
+ (this.connection as { transport?: { close?(): void } }).transport?.close?.();
668
+ } catch {
669
+ /* swallow */
670
+ }
565
671
  this.connection = null;
566
672
  }
567
673
  }
@@ -582,11 +688,18 @@ export class BrowserView {
582
688
 
583
689
  evaluate(script: string, frameId?: string): Promise<EvaluateResult> {
584
690
  if (!this.nativeAttached) {
585
- return Promise.resolve({ ok: false, code: "not_supported", message: "native runtime unavailable" });
691
+ return Promise.resolve({
692
+ ok: false,
693
+ code: "not_supported",
694
+ message: "native runtime unavailable",
695
+ });
586
696
  }
587
697
  return new Promise<EvaluateResult>((resolve) => {
588
698
  let timer: ReturnType<typeof setTimeout> | null = null;
589
- const wrappedResolve = (r: EvaluateResult) => { if (timer) clearTimeout(timer); resolve(r); };
699
+ const wrappedResolve = (r: EvaluateResult) => {
700
+ if (timer) clearTimeout(timer);
701
+ resolve(r);
702
+ };
590
703
  const requestId = registerEvaluateRequest(this.id, wrappedResolve);
591
704
  // 30s timeout — two-hop CDP path (createIsolatedWorld → Runtime.evaluate)
592
705
  // can silently hang if the frame is destroyed mid-flight.
@@ -597,7 +710,12 @@ export class BrowserView {
597
710
  }, 30_000);
598
711
  const lib = getNativeLibrary();
599
712
  if (frameId) {
600
- lib?.symbols.bunite_view_evaluate_in_frame(this.id, requestId, toCString(script), toCString(frameId));
713
+ lib?.symbols.bunite_view_evaluate_in_frame(
714
+ this.id,
715
+ requestId,
716
+ toCString(script),
717
+ toCString(frameId),
718
+ );
601
719
  } else {
602
720
  lib?.symbols.bunite_view_evaluate(this.id, requestId, toCString(script));
603
721
  }
@@ -608,13 +726,19 @@ export class BrowserView {
608
726
  if (!this.nativeAttached) return;
609
727
  const policyCode = policy === "auto" ? 0 : policy === "ask" ? 1 : 2;
610
728
  getNativeLibrary()?.symbols.bunite_view_set_download_policy(
611
- this.id, policyCode, toCString(downloadDir ?? "")
729
+ this.id,
730
+ policyCode,
731
+ toCString(downloadDir ?? ""),
612
732
  );
613
733
  }
614
734
 
615
735
  listFrames(): Promise<ListFramesResult> {
616
736
  if (!this.nativeAttached) {
617
- return Promise.resolve({ ok: false, code: "not_supported", message: "native runtime unavailable" });
737
+ return Promise.resolve({
738
+ ok: false,
739
+ code: "not_supported",
740
+ message: "native runtime unavailable",
741
+ });
618
742
  }
619
743
  return new Promise<ListFramesResult>((resolve) => {
620
744
  const requestId = registerFramesRequest(this.id, resolve);
@@ -623,7 +747,10 @@ export class BrowserView {
623
747
  resolve({ ok: false, code: "runtime_error", message: "list frames timed out after 10s" });
624
748
  }
625
749
  }, 10_000);
626
- const wrappedResolve = (r: ListFramesResult) => { clearTimeout(timer); resolve(r); };
750
+ const wrappedResolve = (r: ListFramesResult) => {
751
+ clearTimeout(timer);
752
+ resolve(r);
753
+ };
627
754
  framesResolvers.set(requestId, { viewId: this.id, resolve: wrappedResolve });
628
755
  getNativeLibrary()?.symbols.bunite_view_list_frames(this.id, requestId);
629
756
  });
@@ -631,22 +758,37 @@ export class BrowserView {
631
758
 
632
759
  resolveAndClick(args: ResolveAndClickArgs): Promise<ResolveAndClickResult> {
633
760
  if (!this.nativeAttached) {
634
- return Promise.resolve({ ok: false, code: "not_supported", message: "native runtime unavailable" });
761
+ return Promise.resolve({
762
+ ok: false,
763
+ code: "not_supported",
764
+ message: "native runtime unavailable",
765
+ });
635
766
  }
636
767
  return new Promise<ResolveAndClickResult>((resolve) => {
637
768
  const requestId = registerResolveAndClickRequest(this.id, resolve);
638
769
  const timer = setTimeout(() => {
639
770
  if (resolveAndClickResolvers.delete(requestId)) {
640
- resolve({ ok: false, code: "runtime_error", message: "resolveAndClick timed out after 5s" });
771
+ resolve({
772
+ ok: false,
773
+ code: "runtime_error",
774
+ message: "resolveAndClick timed out after 5s",
775
+ });
641
776
  }
642
777
  }, 5_000);
643
- const wrappedResolve = (r: ResolveAndClickResult) => { clearTimeout(timer); resolve(r); };
778
+ const wrappedResolve = (r: ResolveAndClickResult) => {
779
+ clearTimeout(timer);
780
+ resolve(r);
781
+ };
644
782
  resolveAndClickResolvers.set(requestId, { viewId: this.id, resolve: wrappedResolve });
645
783
  const button = args.button === "right" ? 2 : args.button === "middle" ? 1 : 0;
646
784
  getNativeLibrary()?.symbols.bunite_view_resolve_and_click(
647
- this.id, requestId,
648
- toCString(args.selector), toCString(args.frameId ?? ""),
649
- button, args.clickCount ?? 1, encodeModifiers(args.modifiers),
785
+ this.id,
786
+ requestId,
787
+ toCString(args.selector),
788
+ toCString(args.frameId ?? ""),
789
+ button,
790
+ args.clickCount ?? 1,
791
+ encodeModifiers(args.modifiers),
650
792
  );
651
793
  });
652
794
  }
@@ -661,7 +803,8 @@ export class BrowserView {
661
803
  // `send*` methods. Modifier translation + key resolution happen inside;
662
804
  // callers never touch the FFI int contract.
663
805
  click(args: {
664
- x: number; y: number;
806
+ x: number;
807
+ y: number;
665
808
  button?: "left" | "middle" | "right";
666
809
  clickCount?: number;
667
810
  modifiers?: Modifier[];
@@ -669,7 +812,12 @@ export class BrowserView {
669
812
  if (!this.nativeAttached) return;
670
813
  const button = args.button === "right" ? 2 : args.button === "middle" ? 1 : 0;
671
814
  getNativeLibrary()?.symbols.bunite_view_click(
672
- this.id, args.x, args.y, button, args.clickCount ?? 1, encodeModifiers(args.modifiers)
815
+ this.id,
816
+ args.x,
817
+ args.y,
818
+ button,
819
+ args.clickCount ?? 1,
820
+ encodeModifiers(args.modifiers),
673
821
  );
674
822
  }
675
823
 
@@ -683,25 +831,35 @@ export class BrowserView {
683
831
  const r = resolveKey(key);
684
832
  const a = action === "down" ? 0 : action === "up" ? 1 : 2;
685
833
  getNativeLibrary()?.symbols.bunite_view_press(
686
- this.id, r.windowsVkCode, r.macKeyCode,
687
- toCString(r.key), toCString(r.code), toCString(r.character),
688
- encodeModifiers(modifiers), a, r.extended, r.location
834
+ this.id,
835
+ r.windowsVkCode,
836
+ r.macKeyCode,
837
+ toCString(r.key),
838
+ toCString(r.code),
839
+ toCString(r.character),
840
+ encodeModifiers(modifiers),
841
+ a,
842
+ r.extended,
843
+ r.location,
689
844
  );
690
845
  }
691
846
 
692
- scroll(args: {
693
- dx: number; dy: number; x?: number; y?: number;
694
- modifiers?: Modifier[];
695
- }) {
847
+ scroll(args: { dx: number; dy: number; x?: number; y?: number; modifiers?: Modifier[] }) {
696
848
  if (!this.nativeAttached) return;
697
849
  getNativeLibrary()?.symbols.bunite_view_scroll(
698
- this.id, args.dx, args.dy, args.x ?? 0, args.y ?? 0, encodeModifiers(args.modifiers)
850
+ this.id,
851
+ args.dx,
852
+ args.dy,
853
+ args.x ?? 0,
854
+ args.y ?? 0,
855
+ encodeModifiers(args.modifiers),
699
856
  );
700
857
  }
701
858
 
702
859
  mouse(args: {
703
860
  action: "move" | "down" | "up";
704
- x: number; y: number;
861
+ x: number;
862
+ y: number;
705
863
  button?: "left" | "middle" | "right";
706
864
  modifiers?: Modifier[];
707
865
  }) {
@@ -709,20 +867,32 @@ export class BrowserView {
709
867
  const action = args.action === "move" ? 0 : args.action === "down" ? 1 : 2;
710
868
  const button = args.button === "right" ? 2 : args.button === "middle" ? 1 : 0;
711
869
  getNativeLibrary()?.symbols.bunite_view_mouse(
712
- this.id, action, args.x, args.y, button, encodeModifiers(args.modifiers)
870
+ this.id,
871
+ action,
872
+ args.x,
873
+ args.y,
874
+ button,
875
+ encodeModifiers(args.modifiers),
713
876
  );
714
877
  }
715
878
 
716
879
  respondToDialog(requestId: number, accept: boolean, text?: string) {
717
880
  if (!this.nativeAttached) return;
718
881
  getNativeLibrary()?.symbols.bunite_view_respond_dialog(
719
- this.id, requestId, accept, toCString(text ?? "")
882
+ this.id,
883
+ requestId,
884
+ accept,
885
+ toCString(text ?? ""),
720
886
  );
721
887
  }
722
888
 
723
889
  screenshot(format: "png" | "jpeg", quality: number): Promise<ScreenshotResult> {
724
890
  if (!this.nativeAttached) {
725
- return Promise.resolve({ ok: false, code: "not_supported", message: "native runtime unavailable" });
891
+ return Promise.resolve({
892
+ ok: false,
893
+ code: "not_supported",
894
+ message: "native runtime unavailable",
895
+ });
726
896
  }
727
897
  return new Promise<ScreenshotResult>((resolve) => {
728
898
  const requestId = registerScreenshotRequest(this.id, resolve);
@@ -732,28 +902,51 @@ export class BrowserView {
732
902
  resolve({ ok: false, code: "timeout", message: "screenshot timed out after 30s" });
733
903
  }
734
904
  }, 30_000);
735
- const wrappedResolve = (r: ScreenshotResult) => { clearTimeout(timer); resolve(r); };
905
+ const wrappedResolve = (r: ScreenshotResult) => {
906
+ clearTimeout(timer);
907
+ resolve(r);
908
+ };
736
909
  // Replace the registered resolver so the timeout-clearing wrapper runs on success.
737
910
  screenshotResolvers.set(requestId, { viewId: this.id, resolve: wrappedResolve });
738
- getNativeLibrary()?.symbols.bunite_view_screenshot(this.id, requestId, toCString(format), quality);
911
+ getNativeLibrary()?.symbols.bunite_view_screenshot(
912
+ this.id,
913
+ requestId,
914
+ toCString(format),
915
+ quality,
916
+ );
739
917
  });
740
918
  }
741
919
 
742
920
  accessibilitySnapshot(interestingOnly: boolean): Promise<AccessibilitySnapshotResult> {
743
921
  if (!this.nativeAttached) {
744
- return Promise.resolve({ ok: false, code: "not_supported", message: "native runtime unavailable" });
922
+ return Promise.resolve({
923
+ ok: false,
924
+ code: "not_supported",
925
+ message: "native runtime unavailable",
926
+ });
745
927
  }
746
928
  return new Promise<AccessibilitySnapshotResult>((resolve) => {
747
929
  const requestId = registerAxRequest(this.id, resolve, interestingOnly);
748
930
  const timer = setTimeout(() => {
749
931
  if (axResolvers.delete(requestId)) {
750
- resolve({ ok: false, code: "timeout", message: "accessibility snapshot timed out after 30s" });
932
+ resolve({
933
+ ok: false,
934
+ code: "timeout",
935
+ message: "accessibility snapshot timed out after 30s",
936
+ });
751
937
  }
752
938
  }, 30_000);
753
- const wrappedResolve = (r: AccessibilitySnapshotResult) => { clearTimeout(timer); resolve(r); };
939
+ const wrappedResolve = (r: AccessibilitySnapshotResult) => {
940
+ clearTimeout(timer);
941
+ resolve(r);
942
+ };
754
943
  axResolvers.set(requestId, { viewId: this.id, resolve: wrappedResolve, interestingOnly });
755
944
  // Native flag is currently unused (filter is TS-side); kept for ABI shape stability.
756
- getNativeLibrary()?.symbols.bunite_view_accessibility_snapshot(this.id, requestId, interestingOnly ? 1 : 0);
945
+ getNativeLibrary()?.symbols.bunite_view_accessibility_snapshot(
946
+ this.id,
947
+ requestId,
948
+ interestingOnly ? 1 : 0,
949
+ );
757
950
  });
758
951
  }
759
952
 
@@ -794,9 +987,7 @@ export class BrowserView {
794
987
  buf[i * 4 + 2] = rects[i].w;
795
988
  buf[i * 4 + 3] = rects[i].h;
796
989
  }
797
- getNativeLibrary()?.symbols.bunite_view_set_mask_region(
798
- this.id, ptr(buf.buffer), rects.length
799
- );
990
+ getNativeLibrary()?.symbols.bunite_view_set_mask_region(this.id, ptr(buf.buffer), rects.length);
800
991
  }
801
992
 
802
993
  bringToFront() {
@@ -868,8 +1059,19 @@ export class BrowserView {
868
1059
  rejectResolveAndClickForView(this.id);
869
1060
  this.nativeAttached = false;
870
1061
  for (const eventName of [
871
- "will-navigate", "did-navigate", "dom-ready", "new-window-open", "permission-requested", "title-changed",
872
- "load-start", "load-finish", "load-fail", "dialog", "console-message", "download-event", "popup-requested",
1062
+ "will-navigate",
1063
+ "did-navigate",
1064
+ "dom-ready",
1065
+ "new-window-open",
1066
+ "permission-requested",
1067
+ "title-changed",
1068
+ "load-start",
1069
+ "load-finish",
1070
+ "load-fail",
1071
+ "dialog",
1072
+ "console-message",
1073
+ "download-event",
1074
+ "popup-requested",
873
1075
  ]) {
874
1076
  buniteEventEmitter.removeAllListeners(`${eventName}-${this.id}`);
875
1077
  }
@@ -877,8 +1079,21 @@ export class BrowserView {
877
1079
  }
878
1080
 
879
1081
  on(
880
- name: "will-navigate" | "did-navigate" | "dom-ready" | "new-window-open" | "permission-requested" | "title-changed" | "load-start" | "load-finish" | "load-fail" | "dialog" | "console-message" | "download-event" | "popup-requested",
881
- handler: (event: unknown) => void
1082
+ name:
1083
+ | "will-navigate"
1084
+ | "did-navigate"
1085
+ | "dom-ready"
1086
+ | "new-window-open"
1087
+ | "permission-requested"
1088
+ | "title-changed"
1089
+ | "load-start"
1090
+ | "load-finish"
1091
+ | "load-fail"
1092
+ | "dialog"
1093
+ | "console-message"
1094
+ | "download-event"
1095
+ | "popup-requested",
1096
+ handler: (event: unknown) => void,
882
1097
  ) {
883
1098
  const specificName = `${name}-${this.id}`;
884
1099
  buniteEventEmitter.on(specificName, handler);
@@ -887,5 +1102,7 @@ export class BrowserView {
887
1102
  }
888
1103
 
889
1104
  attachBrowserViewRegistry({
890
- getById(id) { return BrowserView.getById(id); }
1105
+ getById(id) {
1106
+ return BrowserView.getById(id);
1107
+ },
891
1108
  });