sparkbun 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.
Files changed (131) hide show
  1. package/bin/sparkbun.cjs +18 -0
  2. package/dist-linux-arm64/bsdiff +0 -0
  3. package/dist-linux-arm64/bspatch +0 -0
  4. package/dist-linux-arm64/libElectrobunCore.so +0 -0
  5. package/dist-linux-arm64/libNativeWrapper.so +0 -0
  6. package/dist-linux-arm64/libasar.so +0 -0
  7. package/dist-linux-x64/bsdiff +0 -0
  8. package/dist-linux-x64/bspatch +0 -0
  9. package/dist-linux-x64/libElectrobunCore.so +0 -0
  10. package/dist-linux-x64/libNativeWrapper.so +0 -0
  11. package/dist-linux-x64/libasar.so +0 -0
  12. package/dist-macos-arm64/bsdiff +0 -0
  13. package/dist-macos-arm64/bspatch +0 -0
  14. package/dist-macos-arm64/libElectrobunCore.dylib +0 -0
  15. package/dist-macos-arm64/libNativeWrapper.dylib +0 -0
  16. package/dist-macos-arm64/libasar.dylib +0 -0
  17. package/dist-macos-arm64/libwebgpu_dawn.dylib +0 -0
  18. package/dist-macos-arm64/preload-full.js +885 -0
  19. package/dist-macos-arm64/preload-sandboxed.js +111 -0
  20. package/dist-macos-arm64/process_helper +0 -0
  21. package/dist-win-x64/ElectrobunCore.dll +0 -0
  22. package/dist-win-x64/WebView2Loader.dll +0 -0
  23. package/dist-win-x64/bsdiff.exe +0 -0
  24. package/dist-win-x64/bspatch.exe +0 -0
  25. package/dist-win-x64/libNativeWrapper.dll +0 -0
  26. package/dist-win-x64/zig-asar/arm64/libasar.dll +0 -0
  27. package/dist-win-x64/zig-asar/x64/libasar.dll +0 -0
  28. package/package.json +47 -0
  29. package/scripts/build-and-upload-artifacts.js +207 -0
  30. package/scripts/gen-webgpu-ffi.mjs +162 -0
  31. package/scripts/install-windows-deps.ps1 +80 -0
  32. package/scripts/package-release.js +237 -0
  33. package/scripts/push-version.js +84 -0
  34. package/scripts/update-bun-version.ts +122 -0
  35. package/scripts/update-cef-version.ts +145 -0
  36. package/src/browser/builtinrpcSchema.ts +19 -0
  37. package/src/browser/global.d.ts +36 -0
  38. package/src/browser/index.ts +234 -0
  39. package/src/browser/webviewtag.ts +88 -0
  40. package/src/browser/wgputag.ts +48 -0
  41. package/src/bun/SparkBunConfig.ts +497 -0
  42. package/src/bun/__tests__/ffi-contract.test.ts +105 -0
  43. package/src/bun/core/ApplicationMenu.ts +70 -0
  44. package/src/bun/core/BrowserView.ts +416 -0
  45. package/src/bun/core/BrowserWindow.ts +396 -0
  46. package/src/bun/core/BuildConfig.ts +71 -0
  47. package/src/bun/core/ContextMenu.ts +75 -0
  48. package/src/bun/core/GpuWindow.ts +289 -0
  49. package/src/bun/core/Paths.ts +5 -0
  50. package/src/bun/core/Socket.ts +22 -0
  51. package/src/bun/core/Tray.ts +197 -0
  52. package/src/bun/core/Updater.ts +1131 -0
  53. package/src/bun/core/Utils.ts +487 -0
  54. package/src/bun/core/WGPUView.ts +167 -0
  55. package/src/bun/core/menuRoles.ts +181 -0
  56. package/src/bun/events/ApplicationEvents.ts +22 -0
  57. package/src/bun/events/event.ts +27 -0
  58. package/src/bun/events/eventEmitter.ts +45 -0
  59. package/src/bun/events/trayEvents.ts +11 -0
  60. package/src/bun/events/webviewEvents.ts +39 -0
  61. package/src/bun/events/windowEvents.ts +23 -0
  62. package/src/bun/index.ts +120 -0
  63. package/src/bun/preload/.generated/compiled.ts +2 -0
  64. package/src/bun/preload/build.ts +65 -0
  65. package/src/bun/preload/dragRegions.ts +41 -0
  66. package/src/bun/preload/encryption.ts +86 -0
  67. package/src/bun/preload/events.ts +171 -0
  68. package/src/bun/preload/globals.d.ts +45 -0
  69. package/src/bun/preload/index-sandboxed.ts +28 -0
  70. package/src/bun/preload/index.ts +77 -0
  71. package/src/bun/preload/internalRpc.ts +80 -0
  72. package/src/bun/preload/overlaySync.ts +107 -0
  73. package/src/bun/preload/webviewTag.ts +451 -0
  74. package/src/bun/preload/wgpuTag.ts +246 -0
  75. package/src/bun/proc/linux.md +43 -0
  76. package/src/bun/proc/native.ts +3253 -0
  77. package/src/bun/webGPU.ts +346 -0
  78. package/src/bun/webgpuAdapter.ts +3011 -0
  79. package/src/cli/bun.lockb +0 -0
  80. package/src/cli/index.ts +4653 -0
  81. package/src/cli/package-lock.json +81 -0
  82. package/src/cli/package.json +11 -0
  83. package/src/cli/templates/embedded.ts +2 -0
  84. package/src/core/build.zig +16 -0
  85. package/src/core/main.zig +3378 -0
  86. package/src/extractor/build.zig +22 -0
  87. package/src/installer/installer-template.ts +216 -0
  88. package/src/launcher/main.ts +221 -0
  89. package/src/native/build/libNativeWrapper.so +0 -0
  90. package/src/native/linux/build/nativeWrapper.o +0 -0
  91. package/src/native/linux/cef_loader.cpp +110 -0
  92. package/src/native/linux/cef_loader.h +28 -0
  93. package/src/native/linux/cef_process_helper_linux.cpp +160 -0
  94. package/src/native/linux/nativeWrapper.cpp +11768 -0
  95. package/src/native/macos/cef_process_helper_mac.cc +160 -0
  96. package/src/native/macos/nativeWrapper.mm +9172 -0
  97. package/src/native/shared/accelerator_parser.h +72 -0
  98. package/src/native/shared/app_paths.h +110 -0
  99. package/src/native/shared/asar.h +35 -0
  100. package/src/native/shared/cache_migration.h +244 -0
  101. package/src/native/shared/callbacks.h +57 -0
  102. package/src/native/shared/cef_response_filter.h +189 -0
  103. package/src/native/shared/chromium_flags.h +181 -0
  104. package/src/native/shared/config.h +66 -0
  105. package/src/native/shared/download_event.h +197 -0
  106. package/src/native/shared/ffi_helpers.h +139 -0
  107. package/src/native/shared/glob_match.h +59 -0
  108. package/src/native/shared/json_menu_parser.h +223 -0
  109. package/src/native/shared/mime_types.h +101 -0
  110. package/src/native/shared/navigation_rules.h +98 -0
  111. package/src/native/shared/partition_context.h +137 -0
  112. package/src/native/shared/pending_resize_queue.h +45 -0
  113. package/src/native/shared/permissions.h +118 -0
  114. package/src/native/shared/permissions_cef.h +74 -0
  115. package/src/native/shared/preload_script.h +71 -0
  116. package/src/native/shared/shutdown_guard.h +134 -0
  117. package/src/native/shared/thread_safe_map.h +138 -0
  118. package/src/native/shared/webview_storage.h +91 -0
  119. package/src/native/win/cef_process_helper_win.cpp +143 -0
  120. package/src/native/win/dcomp_compositor.h +352 -0
  121. package/src/native/win/nativeWrapper.cpp +12434 -0
  122. package/src/npmbin/index.js +34 -0
  123. package/src/shared/bun-version.ts +3 -0
  124. package/src/shared/cef-version.ts +5 -0
  125. package/src/shared/naming.test.ts +327 -0
  126. package/src/shared/naming.ts +188 -0
  127. package/src/shared/platform.ts +48 -0
  128. package/src/shared/rpc.ts +541 -0
  129. package/src/shared/sparkbun-version.ts +2 -0
  130. package/src/types/three.d.ts +1 -0
  131. package/tsconfig.json +31 -0
@@ -0,0 +1,416 @@
1
+ import { ffi } from "../proc/native";
2
+ import sparkBunEventEmitter from "../events/eventEmitter";
3
+ import {
4
+ type SparkBunRPCSchema,
5
+ type SparkBunRPCConfig,
6
+ type RPCWithTransport,
7
+ defineSparkBunRPC,
8
+ } from "../../shared/rpc.js";
9
+ import { BuildConfig } from "./BuildConfig";
10
+ import {
11
+ sendMessageToWebviewViaSocket,
12
+ removeSocketForWebview,
13
+ } from "./Socket";
14
+ import { randomBytes } from "crypto";
15
+ import { type Pointer } from "bun:ffi";
16
+
17
+ const BrowserViewMap: {
18
+ [id: number]: BrowserView<any>;
19
+ } = {};
20
+
21
+ export type BrowserViewOptions<T = undefined> = {
22
+ url: string | null;
23
+ html: string | null;
24
+ preload: string | null;
25
+ viewsRoot: string | null;
26
+ renderer: "native" | "cef";
27
+ partition: string | null;
28
+ frame: {
29
+ x: number;
30
+ y: number;
31
+ width: number;
32
+ height: number;
33
+ };
34
+ rpc: T;
35
+ hostWebviewId: number;
36
+ autoResize: boolean;
37
+ windowId: number;
38
+ navigationRules: string | null;
39
+ // Sandbox mode: when true, disables RPC and only allows event emission
40
+ // Use for untrusted content (remote URLs) to prevent malicious sites from
41
+ // accessing internal APIs, creating OOPIFs, or communicating with Bun
42
+ sandbox: boolean;
43
+ // Set transparent on the AbstractView at creation (before first paint)
44
+ startTransparent: boolean;
45
+ // Set passthrough on the AbstractView at creation (before first paint)
46
+ startPassthrough: boolean;
47
+ // renderer:
48
+ };
49
+
50
+ const buildConfig = BuildConfig.getSync();
51
+
52
+ const defaultOptions: Partial<BrowserViewOptions> = {
53
+ url: null,
54
+ html: null,
55
+ preload: null,
56
+ viewsRoot: null,
57
+ renderer: buildConfig.defaultRenderer,
58
+ frame: {
59
+ x: 0,
60
+ y: 0,
61
+ width: 800,
62
+ height: 600,
63
+ },
64
+ };
65
+ export class BrowserView<T extends RPCWithTransport = RPCWithTransport> {
66
+ id = 0;
67
+ hostWebviewId?: number;
68
+ windowId!: number;
69
+ renderer!: "cef" | "native";
70
+ url: string | null = null;
71
+ html: string | null = null;
72
+ preload: string | null = null;
73
+ viewsRoot: string | null = null;
74
+ partition: string | null = null;
75
+ autoResize: boolean = true;
76
+ frame: {
77
+ x: number;
78
+ y: number;
79
+ width: number;
80
+ height: number;
81
+ } = {
82
+ x: 0,
83
+ y: 0,
84
+ width: 800,
85
+ height: 600,
86
+ };
87
+ secretKey!: Uint8Array;
88
+ rpc?: T;
89
+ rpcHandler?: (msg: unknown) => void;
90
+ navigationRules: string | null = null;
91
+ // Sandbox mode disables RPC and only allows event emission (for untrusted content)
92
+ sandbox: boolean = false;
93
+ startTransparent: boolean = false;
94
+ startPassthrough: boolean = false;
95
+ isRemoved: boolean = false;
96
+
97
+ get ptr(): Pointer | null {
98
+ if (this.isRemoved) {
99
+ return null;
100
+ }
101
+ return ffi.request.getWebviewPointer({ id: this.id }) as Pointer | null;
102
+ }
103
+
104
+ constructor(options: Partial<BrowserViewOptions<T>> = defaultOptions) {
105
+ // const rpc = options.rpc;
106
+
107
+ this.url = options.url || defaultOptions.url || null;
108
+ this.html = options.html || defaultOptions.html || null;
109
+ this.preload = options.preload || defaultOptions.preload || null;
110
+ this.viewsRoot = options.viewsRoot || defaultOptions.viewsRoot || null;
111
+ this.frame = {
112
+ x: options.frame?.x ?? defaultOptions.frame!.x,
113
+ y: options.frame?.y ?? defaultOptions.frame!.y,
114
+ width: options.frame?.width ?? defaultOptions.frame!.width,
115
+ height: options.frame?.height ?? defaultOptions.frame!.height,
116
+ };
117
+ this.rpc = options.rpc;
118
+ this.secretKey = new Uint8Array(randomBytes(32));
119
+ this.partition = options.partition || null;
120
+ this.hostWebviewId = options.hostWebviewId;
121
+ this.windowId = options.windowId ?? 0;
122
+ this.autoResize = options.autoResize === false ? false : true;
123
+ this.navigationRules = options.navigationRules || null;
124
+ this.renderer = options.renderer ?? defaultOptions.renderer ?? "native";
125
+ this.sandbox = options.sandbox ?? false;
126
+ this.startTransparent = options.startTransparent ?? false;
127
+ this.startPassthrough = options.startPassthrough ?? false;
128
+
129
+ this.id = this.init() as number;
130
+ BrowserViewMap[this.id] = this;
131
+
132
+ // If HTML content was provided, load it after webview creation.
133
+ if (this.html) {
134
+ setTimeout(() => {
135
+ this.loadHTML(this.html!);
136
+ }, 100);
137
+ }
138
+ }
139
+
140
+ init() {
141
+ this.initializeRpcTransport();
142
+
143
+ return ffi.request.createWebview({
144
+ windowId: this.windowId,
145
+ hostWebviewId: this.hostWebviewId ?? null,
146
+ renderer: this.renderer,
147
+ // todo: consider sending secretKey as base64
148
+ secretKey: this.secretKey.toString(),
149
+ partition: this.partition,
150
+ // Only pass URL if no HTML content is provided to avoid conflicts
151
+ url: this.html ? null : this.url,
152
+ preload: this.preload,
153
+ viewsRoot: this.viewsRoot,
154
+ frame: {
155
+ width: this.frame.width,
156
+ height: this.frame.height,
157
+ x: this.frame.x,
158
+ y: this.frame.y,
159
+ },
160
+ autoResize: this.autoResize,
161
+ navigationRules: this.navigationRules,
162
+ sandbox: this.sandbox,
163
+ startTransparent: this.startTransparent,
164
+ startPassthrough: this.startPassthrough,
165
+ // transparent is looked up from parent window in native.ts
166
+ });
167
+ }
168
+
169
+ initializeRpcTransport() {
170
+ if (!this.rpc) {
171
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
172
+ this.rpc = BrowserView.defineRPC({
173
+ handlers: { requests: {}, messages: {} },
174
+ }) as any;
175
+ }
176
+
177
+ this.rpc!.setTransport(this.createTransport());
178
+ }
179
+
180
+ sendHostMessageToWebviewViaExecute(jsonMessage: unknown) {
181
+ const stringifiedMessage =
182
+ typeof jsonMessage === "string"
183
+ ? jsonMessage
184
+ : JSON.stringify(jsonMessage);
185
+ const wrappedMessage = `window.__sparkbun.receiveMessageFromHost(${stringifiedMessage})`;
186
+ this.executeJavascript(wrappedMessage);
187
+ }
188
+
189
+ sendInternalHostMessageViaExecute(jsonMessage: unknown) {
190
+ const stringifiedMessage =
191
+ typeof jsonMessage === "string"
192
+ ? jsonMessage
193
+ : JSON.stringify(jsonMessage);
194
+ const wrappedMessage = `window.__sparkbun.receiveInternalMessageFromHost(${stringifiedMessage})`;
195
+ this.executeJavascript(wrappedMessage);
196
+ }
197
+
198
+ // Note: the OS has a buffer limit on named pipes. If we overflow it
199
+ // it won't trigger the kevent for zig to read the pipe and we'll be stuck.
200
+ // so we have to chunk it
201
+ // TODO: is this still needed after switching from named pipes
202
+ executeJavascript(js: string) {
203
+ if (!this.ptr || this.isRemoved) {
204
+ return;
205
+ }
206
+ ffi.request.evaluateJavascriptWithNoCompletion({ id: this.id, js });
207
+ }
208
+
209
+ loadURL(url: string) {
210
+ this.url = url;
211
+ ffi.request.loadURLInWebView({ id: this.id, url: this.url });
212
+ }
213
+
214
+ loadHTML(html: string) {
215
+ this.html = html;
216
+
217
+ if (this.renderer === "cef") {
218
+ // For CEF, store HTML content in native map and use scheme handler
219
+ ffi.request.setWebviewHTMLContent({ id: this.id, html });
220
+ this.loadURL("views://internal/index.html");
221
+ } else {
222
+ // For WKWebView, load HTML content directly
223
+ ffi.request.loadHTMLInWebView({ id: this.id, html });
224
+ }
225
+ }
226
+
227
+ setNavigationRules(rules: string[]) {
228
+ this.navigationRules = JSON.stringify(rules);
229
+ const rulesJson = JSON.stringify(rules);
230
+ ffi.request.setWebviewNavigationRules({ id: this.id, rulesJson });
231
+ }
232
+
233
+ findInPage(
234
+ searchText: string,
235
+ options?: { forward?: boolean; matchCase?: boolean },
236
+ ) {
237
+ const forward = options?.forward ?? true;
238
+ const matchCase = options?.matchCase ?? false;
239
+ ffi.request.webviewFindInPage({
240
+ id: this.id,
241
+ searchText,
242
+ forward,
243
+ matchCase,
244
+ });
245
+ }
246
+
247
+ stopFindInPage() {
248
+ ffi.request.webviewStopFind({ id: this.id });
249
+ }
250
+
251
+ openDevTools() {
252
+ ffi.request.webviewOpenDevTools({ id: this.id });
253
+ }
254
+
255
+ closeDevTools() {
256
+ ffi.request.webviewCloseDevTools({ id: this.id });
257
+ }
258
+
259
+ toggleDevTools() {
260
+ ffi.request.webviewToggleDevTools({ id: this.id });
261
+ }
262
+
263
+ /**
264
+ * Set the page zoom level (WebKit only, similar to browser zoom).
265
+ * @param zoomLevel - The zoom level (1.0 = 100%, 1.5 = 150%, etc.)
266
+ */
267
+ setPageZoom(zoomLevel: number) {
268
+ ffi.request.webviewSetPageZoom({ id: this.id, zoomLevel });
269
+ }
270
+
271
+ /**
272
+ * Get the current page zoom level.
273
+ * @returns The current zoom level (1.0 = 100%)
274
+ */
275
+ getPageZoom(): number {
276
+ return ffi.request.webviewGetPageZoom({ id: this.id }) as number;
277
+ }
278
+
279
+ // name should only allow browserView events
280
+ // Note: normalize event names to willNavigate instead of ['will-navigate'] to save
281
+ // 5 characters per usage and allow minification to be more effective.
282
+ on(
283
+ name:
284
+ | "will-navigate"
285
+ | "did-navigate"
286
+ | "did-navigate-in-page"
287
+ | "did-commit-navigation"
288
+ | "dom-ready"
289
+ | "download-started"
290
+ | "download-progress"
291
+ | "download-completed"
292
+ | "download-failed",
293
+ handler: (event: unknown) => void,
294
+ ) {
295
+ const specificName = `${name}-${this.id}`;
296
+ sparkBunEventEmitter.on(specificName, handler);
297
+ }
298
+
299
+ createTransport = () => {
300
+ const that = this;
301
+
302
+ return {
303
+ send(message: any) {
304
+ if (!that.ptr || that.isRemoved) {
305
+ return;
306
+ }
307
+ const sentOverSocket = sendMessageToWebviewViaSocket(that.id, message);
308
+
309
+ if (!sentOverSocket) {
310
+ try {
311
+ const messageString = JSON.stringify(message);
312
+ that.sendHostMessageToWebviewViaExecute(messageString);
313
+ } catch (error) {
314
+ console.error("host: failed to serialize message to webview", error);
315
+ }
316
+ }
317
+ },
318
+ registerHandler(handler: (msg: unknown) => void) {
319
+ if (that.isRemoved) {
320
+ return;
321
+ }
322
+ that.rpcHandler = handler;
323
+ },
324
+ };
325
+ };
326
+
327
+ remove() {
328
+ if (this.isRemoved) {
329
+ return;
330
+ }
331
+ this.isRemoved = true;
332
+ // Drop JS-side references first so late callbacks cannot target a stale view.
333
+ delete BrowserViewMap[this.id];
334
+ removeSocketForWebview(this.id);
335
+ this.rpc?.setTransport({
336
+ send() {},
337
+ registerHandler() {},
338
+ unregisterHandler() {},
339
+ });
340
+ this.rpcHandler = undefined;
341
+ try {
342
+ ffi.request.webviewRemove({ id: this.id });
343
+ } catch (error) {
344
+ console.error(`Error removing webview ${this.id}:`, error);
345
+ }
346
+ }
347
+
348
+ static getById(id: number) {
349
+ return BrowserViewMap[id];
350
+ }
351
+
352
+ // Core can create webviews before Bun has constructed a JS wrapper for them.
353
+ // Use this in native/runtime paths that need to ensure a wrapper exists.
354
+ static ensureWrapped<T extends RPCWithTransport = RPCWithTransport>(
355
+ id: number,
356
+ options: Partial<BrowserViewOptions<T>> = {},
357
+ ) {
358
+ return (
359
+ (BrowserViewMap[id] as BrowserView<T> | undefined) ??
360
+ BrowserView.adoptExisting(id, options)
361
+ );
362
+ }
363
+
364
+ static adoptExisting<T extends RPCWithTransport = RPCWithTransport>(
365
+ id: number,
366
+ options: Partial<BrowserViewOptions<T>> = {},
367
+ ) {
368
+ const existing = BrowserViewMap[id] as BrowserView<T> | undefined;
369
+ if (existing) {
370
+ return existing;
371
+ }
372
+
373
+ const ptr = ffi.request.getWebviewPointer({ id }) as Pointer | null;
374
+ if (!ptr) {
375
+ return undefined;
376
+ }
377
+
378
+ const view = Object.create(BrowserView.prototype) as BrowserView<T>;
379
+ view.id = id;
380
+ view.hostWebviewId = options.hostWebviewId;
381
+ view.windowId = options.windowId ?? 0;
382
+ view.renderer = options.renderer ?? defaultOptions.renderer ?? "native";
383
+ view.url = options.url ?? defaultOptions.url ?? null;
384
+ view.html = options.html ?? defaultOptions.html ?? null;
385
+ view.preload = options.preload ?? defaultOptions.preload ?? null;
386
+ view.viewsRoot = options.viewsRoot ?? defaultOptions.viewsRoot ?? null;
387
+ view.partition = options.partition ?? null;
388
+ view.frame = {
389
+ x: options.frame?.x ?? defaultOptions.frame!.x,
390
+ y: options.frame?.y ?? defaultOptions.frame!.y,
391
+ width: options.frame?.width ?? defaultOptions.frame!.width,
392
+ height: options.frame?.height ?? defaultOptions.frame!.height,
393
+ };
394
+ view.secretKey = new Uint8Array(0);
395
+ view.rpc = options.rpc;
396
+ view.rpcHandler = undefined;
397
+ view.autoResize = options.autoResize === false ? false : true;
398
+ view.navigationRules = options.navigationRules ?? null;
399
+ view.sandbox = options.sandbox ?? false;
400
+ view.startTransparent = options.startTransparent ?? false;
401
+ view.startPassthrough = options.startPassthrough ?? false;
402
+ view.isRemoved = false;
403
+ BrowserViewMap[id] = view as BrowserView<any>;
404
+ return view;
405
+ }
406
+
407
+ static getAll() {
408
+ return Object.values(BrowserViewMap);
409
+ }
410
+
411
+ static defineRPC<Schema extends SparkBunRPCSchema>(
412
+ config: SparkBunRPCConfig<Schema, "bun">,
413
+ ) {
414
+ return defineSparkBunRPC("bun", config);
415
+ }
416
+ }