bunite-core 0.6.0 → 0.8.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.
Files changed (39) hide show
  1. package/package.json +4 -2
  2. package/src/bun/core/App.ts +35 -34
  3. package/src/bun/core/BrowserView.ts +3 -9
  4. package/src/bun/core/singleInstanceLock.ts +91 -0
  5. package/src/bun/index.ts +5 -6
  6. package/src/bun/preload/inline.ts +1 -2
  7. package/src/bun/proc/native.ts +54 -78
  8. package/src/native/linux/bunite_linux_appres.cpp +173 -0
  9. package/src/native/linux/bunite_linux_ffi.cpp +263 -0
  10. package/src/native/linux/bunite_linux_internal.h +148 -0
  11. package/src/native/linux/bunite_linux_preload.cpp +1 -0
  12. package/src/native/linux/bunite_linux_runtime.cpp +120 -0
  13. package/src/native/linux/bunite_linux_utils.cpp +114 -0
  14. package/src/native/linux/bunite_linux_view.cpp +244 -0
  15. package/src/native/linux/bunite_linux_window.cpp +101 -0
  16. package/src/native/mac/bunite_mac_appres.mm +163 -0
  17. package/src/native/mac/bunite_mac_ffi.mm +470 -0
  18. package/src/native/mac/bunite_mac_internal.h +151 -0
  19. package/src/native/mac/bunite_mac_runtime.mm +15 -0
  20. package/src/native/mac/bunite_mac_utils.mm +121 -0
  21. package/src/native/mac/bunite_mac_view.mm +279 -0
  22. package/src/native/mac/bunite_mac_window.mm +187 -0
  23. package/src/native/shared/ffi_exports.h +13 -13
  24. package/src/native/shared/permissions.h +14 -0
  25. package/src/native/shared/webview_storage.h +6 -3
  26. package/src/native/win/native_host_cef.cpp +4 -8
  27. package/src/native/win/native_host_ffi.cpp +76 -123
  28. package/src/native/win/native_host_internal.h +5 -3
  29. package/src/native/win/native_host_runtime.cpp +2 -6
  30. package/src/native/win/native_host_utils.cpp +23 -52
  31. package/src/native/win/process_helper_win.cpp +1 -3
  32. package/src/preload/runtime.ts +1 -3
  33. package/src/preload/tsconfig.tsbuildinfo +1 -1
  34. package/src/preload/webviewElement.ts +3 -8
  35. package/src/shared/paths.ts +35 -44
  36. package/src/shared/platform.ts +1 -2
  37. package/src/shared/rpcDemux.ts +47 -2
  38. package/src/shared/webviewPolyfill.ts +64 -6
  39. package/src/bun/core/Utils.ts +0 -301
@@ -1,301 +0,0 @@
1
- import { log } from "../../shared/log";
2
- import { ensureNativeRuntime, showNativeMessageBox } from "../proc/native";
3
- import { BrowserView } from "./BrowserView";
4
- import { BrowserWindow, getLastFocusedWindowId } from "./BrowserWindow";
5
-
6
- export type MessageBoxOptions = {
7
- windowId?: number;
8
- type?: "none" | "info" | "warning" | "error" | "question";
9
- title?: string;
10
- message?: string;
11
- detail?: string;
12
- buttons?: string[];
13
- defaultId?: number;
14
- cancelId?: number;
15
- browser?: boolean;
16
- };
17
-
18
- export type MessageBoxResponse = {
19
- response: number;
20
- };
21
-
22
- // ---------------------------------------------------------------------------
23
- // Pending request tracking
24
- // ---------------------------------------------------------------------------
25
-
26
- let nextRequestId = 1;
27
-
28
- type PendingMessageBox = {
29
- viewId: number;
30
- fallbackResponse: number;
31
- resolve: (response: number) => void;
32
- timeoutId: ReturnType<typeof setTimeout>;
33
- };
34
-
35
- const pendingMessageBoxes = new Map<number, PendingMessageBox>();
36
-
37
- export function handleMessageBoxResponse(requestId: number, response: number): boolean {
38
- const pending = pendingMessageBoxes.get(requestId);
39
- if (!pending) {
40
- return false;
41
- }
42
- clearTimeout(pending.timeoutId);
43
- pendingMessageBoxes.delete(requestId);
44
- pending.resolve(typeof response === "number" && response >= 0 ? response : pending.fallbackResponse);
45
- return true;
46
- }
47
-
48
- export function cancelPendingMessageBoxesForView(viewId: number): void {
49
- for (const [requestId, pending] of pendingMessageBoxes) {
50
- if (pending.viewId === viewId) {
51
- clearTimeout(pending.timeoutId);
52
- pendingMessageBoxes.delete(requestId);
53
- pending.resolve(pending.fallbackResponse);
54
- }
55
- }
56
- }
57
-
58
- // ---------------------------------------------------------------------------
59
- // Preferred view selection
60
- // ---------------------------------------------------------------------------
61
-
62
- function getPreferredMessageBoxView(): BrowserView | null {
63
- const allViews = BrowserView.getAll();
64
- if (allViews.length === 0) {
65
- return null;
66
- }
67
-
68
- const focusedWindowId = getLastFocusedWindowId();
69
- if (focusedWindowId != null) {
70
- const view = allViews.find(v => v.windowId === focusedWindowId);
71
- if (view) return view;
72
- }
73
-
74
- const allWindows = BrowserWindow.getAll();
75
- for (const win of allWindows) {
76
- const view = allViews.find(v => v.windowId === win.id);
77
- if (view) return view;
78
- }
79
-
80
- return allViews[0] ?? null;
81
- }
82
-
83
- // ---------------------------------------------------------------------------
84
- // Dialog script builder
85
- // ---------------------------------------------------------------------------
86
-
87
- function escapeJs(value: string): string {
88
- return value
89
- .replace(/\\/g, "\\\\")
90
- .replace(/"/g, '\\"')
91
- .replace(/\n/g, "\\n")
92
- .replace(/\r/g, "\\r")
93
- .replace(/\t/g, "\\t");
94
- }
95
-
96
- function buildBrowserMessageBoxScript(
97
- requestId: number,
98
- options: Required<Pick<MessageBoxOptions, "type">> & MessageBoxOptions
99
- ): string {
100
- const buttons = options.buttons && options.buttons.length > 0 ? options.buttons : ["OK"];
101
- const defaultId = Math.max(0, Math.min(options.defaultId ?? 0, buttons.length - 1));
102
- const cancelId = options.cancelId != null && options.cancelId >= 0
103
- ? Math.max(0, Math.min(options.cancelId, buttons.length - 1))
104
- : options.cancelId ?? -1;
105
-
106
- const buttonsJson = JSON.stringify(buttons);
107
-
108
- return `(() => {
109
- const spec = {
110
- requestId: ${requestId},
111
- type: "${escapeJs(options.type ?? "info")}",
112
- title: "${escapeJs(options.title ?? "")}",
113
- message: "${escapeJs(options.message ?? "")}",
114
- detail: "${escapeJs(options.detail ?? "")}",
115
- buttons: ${buttonsJson},
116
- defaultId: ${defaultId},
117
- cancelId: ${cancelId}
118
- };
119
- const rootId = \`__bunite_message_box_\${spec.requestId}\`;
120
- if (document.getElementById(rootId)) {
121
- return;
122
- }
123
-
124
- const submit = (response) => {
125
- void (typeof bunite !== "undefined" && bunite.invoke
126
- ? bunite.invoke("__bunite:messageBoxResponse", { requestId: spec.requestId, response }).catch(() => {})
127
- : Promise.resolve());
128
- };
129
-
130
- const mount = () => {
131
- const host = document.body ?? document.documentElement;
132
- if (!host) {
133
- return;
134
- }
135
-
136
- const overlay = document.createElement("div");
137
- overlay.id = rootId;
138
- overlay.dataset.buniteMessageBox = "true";
139
- overlay.dataset.buniteMessageBoxRequestId = String(spec.requestId);
140
- overlay.tabIndex = -1;
141
- overlay.style.cssText = [
142
- "position:fixed",
143
- "inset:0",
144
- "display:flex",
145
- "align-items:center",
146
- "justify-content:center",
147
- "padding:24px",
148
- "background:rgba(15,23,42,0.42)",
149
- "backdrop-filter:blur(6px)",
150
- "z-index:2147483647",
151
- "font-family:Segoe UI, Arial, sans-serif"
152
- ].join(";");
153
-
154
- const panel = document.createElement("div");
155
- panel.style.cssText = [
156
- "width:min(480px, calc(100vw - 48px))",
157
- "border-radius:16px",
158
- "border:1px solid rgba(15,23,42,0.10)",
159
- "background:#ffffff",
160
- "box-shadow:0 24px 80px rgba(15,23,42,0.28)",
161
- "padding:20px 20px 18px",
162
- "color:#0f172a"
163
- ].join(";");
164
-
165
- const accent = document.createElement("div");
166
- const accentColor =
167
- spec.type === "error" ? "#dc2626" :
168
- spec.type === "warning" ? "#d97706" :
169
- spec.type === "question" ? "#2563eb" :
170
- "#0f766e";
171
- accent.style.cssText = \`width:48px;height:4px;border-radius:999px;background:\${accentColor};margin-bottom:14px;\`;
172
- panel.appendChild(accent);
173
-
174
- if (spec.title) {
175
- const heading = document.createElement("h1");
176
- heading.textContent = spec.title;
177
- heading.style.cssText = "margin:0 0 8px;font-size:20px;line-height:1.25;font-weight:700;";
178
- panel.appendChild(heading);
179
- }
180
-
181
- if (spec.message) {
182
- const body = document.createElement("p");
183
- body.textContent = spec.message;
184
- body.style.cssText = "margin:0;font-size:14px;line-height:1.55;white-space:pre-wrap;";
185
- panel.appendChild(body);
186
- }
187
-
188
- if (spec.detail) {
189
- const detail = document.createElement("p");
190
- detail.textContent = spec.detail;
191
- detail.style.cssText = "margin:10px 0 0;font-size:12px;line-height:1.55;color:#475569;white-space:pre-wrap;";
192
- panel.appendChild(detail);
193
- }
194
-
195
- const buttonRow = document.createElement("div");
196
- buttonRow.style.cssText = "display:flex;justify-content:flex-end;gap:10px;flex-wrap:wrap;margin-top:18px;";
197
- spec.buttons.forEach((label, index) => {
198
- const button = document.createElement("button");
199
- button.type = "button";
200
- button.textContent = label;
201
- button.dataset.buniteMessageBoxButtonIndex = String(index);
202
- button.style.cssText =
203
- index === spec.defaultId
204
- ? "appearance:none;border:0;border-radius:999px;background:#111827;color:#ffffff;padding:10px 16px;font:600 13px Segoe UI, Arial, sans-serif;cursor:pointer;"
205
- : "appearance:none;border:1px solid rgba(15,23,42,0.14);border-radius:999px;background:#f8fafc;color:#0f172a;padding:10px 16px;font:600 13px Segoe UI, Arial, sans-serif;cursor:pointer;";
206
- button.addEventListener("click", () => {
207
- overlay.remove();
208
- submit(index);
209
- });
210
- buttonRow.appendChild(button);
211
- });
212
- panel.appendChild(buttonRow);
213
- overlay.appendChild(panel);
214
- host.appendChild(overlay);
215
-
216
- overlay.addEventListener("click", (event) => {
217
- if (event.target !== overlay) {
218
- return;
219
- }
220
- overlay.remove();
221
- submit(spec.cancelId >= 0 ? spec.cancelId : spec.defaultId);
222
- });
223
-
224
- overlay.addEventListener("keydown", (event) => {
225
- if (event.key !== "Escape") {
226
- return;
227
- }
228
- event.preventDefault();
229
- overlay.remove();
230
- submit(spec.cancelId >= 0 ? spec.cancelId : spec.defaultId);
231
- });
232
-
233
- requestAnimationFrame(() => {
234
- overlay.focus();
235
- const defaultButton = overlay.querySelector(\`[data-bunite-message-box-button-index="\${spec.defaultId}"]\`);
236
- if (defaultButton instanceof HTMLButtonElement) {
237
- defaultButton.focus();
238
- }
239
- });
240
- };
241
-
242
- if (document.readyState === "loading") {
243
- document.addEventListener("DOMContentLoaded", mount, { once: true });
244
- } else {
245
- mount();
246
- }
247
- })();`;
248
- }
249
-
250
- // ---------------------------------------------------------------------------
251
- // Public API
252
- // ---------------------------------------------------------------------------
253
-
254
- export async function showMessageBox(
255
- options: MessageBoxOptions = {}
256
- ): Promise<MessageBoxResponse> {
257
- ensureNativeRuntime();
258
-
259
- const windowId = options.windowId ?? 0;
260
-
261
- if (options.browser) {
262
- const view = getPreferredMessageBoxView();
263
- if (view) {
264
- const requestId = nextRequestId++;
265
- const fallbackResponse = options.cancelId ?? options.defaultId ?? 0;
266
- const script = buildBrowserMessageBoxScript(requestId, {
267
- type: options.type ?? "info",
268
- ...options
269
- });
270
-
271
- view.bringToFront();
272
-
273
- const response = await new Promise<number>((resolve) => {
274
- const timeoutId = setTimeout(() => {
275
- pendingMessageBoxes.delete(requestId);
276
- resolve(fallbackResponse);
277
- }, 15_000);
278
-
279
- pendingMessageBoxes.set(requestId, {
280
- viewId: view.id,
281
- fallbackResponse,
282
- resolve,
283
- timeoutId
284
- });
285
-
286
- view.executeJavaScript(script);
287
- });
288
-
289
- return { response };
290
- }
291
- }
292
-
293
- return { response: showNativeMessageBox(windowId, options) };
294
- }
295
-
296
- export function showMessageBoxSync(
297
- options: MessageBoxOptions = {}
298
- ): MessageBoxResponse {
299
- ensureNativeRuntime();
300
- return { response: showNativeMessageBox(options.windowId ?? 0, options) };
301
- }