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,181 @@
1
+ /**
2
+ * Shared menu role definitions used by ApplicationMenu, ContextMenu, and Tray menus.
3
+ * These map to macOS NSResponder selectors for native text editing support.
4
+ */
5
+
6
+ export const roleLabelMap: Record<string, string> = {
7
+ // Application roles
8
+ about: "About",
9
+ quit: "Quit",
10
+ hide: "Hide",
11
+ hideOthers: "Hide Others",
12
+ showAll: "Show All",
13
+
14
+ // Window roles
15
+ minimize: "Minimize",
16
+ zoom: "Zoom",
17
+ close: "Close",
18
+ bringAllToFront: "Bring All To Front",
19
+ cycleThroughWindows: "Cycle Through Windows",
20
+ enterFullScreen: "Enter Full Screen",
21
+ exitFullScreen: "Exit Full Screen",
22
+ toggleFullScreen: "Toggle Full Screen",
23
+
24
+ // Standard edit roles
25
+ undo: "Undo",
26
+ redo: "Redo",
27
+ cut: "Cut",
28
+ copy: "Copy",
29
+ paste: "Paste",
30
+ pasteAndMatchStyle: "Paste and Match Style",
31
+ delete: "Delete",
32
+ selectAll: "Select All",
33
+
34
+ // Speech roles
35
+ startSpeaking: "Start Speaking",
36
+ stopSpeaking: "Stop Speaking",
37
+
38
+ // Help
39
+ showHelp: "Show Help",
40
+
41
+ // Movement - basic
42
+ moveForward: "Move Forward",
43
+ moveBackward: "Move Backward",
44
+ moveLeft: "Move Left",
45
+ moveRight: "Move Right",
46
+ moveUp: "Move Up",
47
+ moveDown: "Move Down",
48
+
49
+ // Movement - by word
50
+ moveWordForward: "Move Word Forward",
51
+ moveWordBackward: "Move Word Backward",
52
+ moveWordLeft: "Move Word Left",
53
+ moveWordRight: "Move Word Right",
54
+
55
+ // Movement - by line
56
+ moveToBeginningOfLine: "Move to Beginning of Line",
57
+ moveToEndOfLine: "Move to End of Line",
58
+ moveToLeftEndOfLine: "Move to Left End of Line",
59
+ moveToRightEndOfLine: "Move to Right End of Line",
60
+
61
+ // Movement - by paragraph
62
+ moveToBeginningOfParagraph: "Move to Beginning of Paragraph",
63
+ moveToEndOfParagraph: "Move to End of Paragraph",
64
+ moveParagraphForward: "Move Paragraph Forward",
65
+ moveParagraphBackward: "Move Paragraph Backward",
66
+
67
+ // Movement - by document
68
+ moveToBeginningOfDocument: "Move to Beginning of Document",
69
+ moveToEndOfDocument: "Move to End of Document",
70
+
71
+ // Movement with selection - basic
72
+ moveForwardAndModifySelection: "Move Forward and Modify Selection",
73
+ moveBackwardAndModifySelection: "Move Backward and Modify Selection",
74
+ moveLeftAndModifySelection: "Move Left and Modify Selection",
75
+ moveRightAndModifySelection: "Move Right and Modify Selection",
76
+ moveUpAndModifySelection: "Move Up and Modify Selection",
77
+ moveDownAndModifySelection: "Move Down and Modify Selection",
78
+
79
+ // Movement with selection - by word
80
+ moveWordForwardAndModifySelection: "Move Word Forward and Modify Selection",
81
+ moveWordBackwardAndModifySelection: "Move Word Backward and Modify Selection",
82
+ moveWordLeftAndModifySelection: "Move Word Left and Modify Selection",
83
+ moveWordRightAndModifySelection: "Move Word Right and Modify Selection",
84
+
85
+ // Movement with selection - by line
86
+ moveToBeginningOfLineAndModifySelection:
87
+ "Move to Beginning of Line and Modify Selection",
88
+ moveToEndOfLineAndModifySelection:
89
+ "Move to End of Line and Modify Selection",
90
+ moveToLeftEndOfLineAndModifySelection:
91
+ "Move to Left End of Line and Modify Selection",
92
+ moveToRightEndOfLineAndModifySelection:
93
+ "Move to Right End of Line and Modify Selection",
94
+
95
+ // Movement with selection - by paragraph
96
+ moveToBeginningOfParagraphAndModifySelection:
97
+ "Move to Beginning of Paragraph and Modify Selection",
98
+ moveToEndOfParagraphAndModifySelection:
99
+ "Move to End of Paragraph and Modify Selection",
100
+ moveParagraphForwardAndModifySelection:
101
+ "Move Paragraph Forward and Modify Selection",
102
+ moveParagraphBackwardAndModifySelection:
103
+ "Move Paragraph Backward and Modify Selection",
104
+
105
+ // Movement with selection - by document
106
+ moveToBeginningOfDocumentAndModifySelection:
107
+ "Move to Beginning of Document and Modify Selection",
108
+ moveToEndOfDocumentAndModifySelection:
109
+ "Move to End of Document and Modify Selection",
110
+
111
+ // Page movement
112
+ pageUp: "Page Up",
113
+ pageDown: "Page Down",
114
+ pageUpAndModifySelection: "Page Up and Modify Selection",
115
+ pageDownAndModifySelection: "Page Down and Modify Selection",
116
+
117
+ // Scrolling
118
+ scrollLineUp: "Scroll Line Up",
119
+ scrollLineDown: "Scroll Line Down",
120
+ scrollPageUp: "Scroll Page Up",
121
+ scrollPageDown: "Scroll Page Down",
122
+ scrollToBeginningOfDocument: "Scroll to Beginning of Document",
123
+ scrollToEndOfDocument: "Scroll to End of Document",
124
+ centerSelectionInVisibleArea: "Center Selection in Visible Area",
125
+
126
+ // Deletion - character
127
+ deleteBackward: "Delete Backward",
128
+ deleteForward: "Delete Forward",
129
+ deleteBackwardByDecomposingPreviousCharacter:
130
+ "Delete Backward by Decomposing Previous Character",
131
+
132
+ // Deletion - word
133
+ deleteWordBackward: "Delete Word Backward",
134
+ deleteWordForward: "Delete Word Forward",
135
+
136
+ // Deletion - line
137
+ deleteToBeginningOfLine: "Delete to Beginning of Line",
138
+ deleteToEndOfLine: "Delete to End of Line",
139
+
140
+ // Deletion - paragraph
141
+ deleteToBeginningOfParagraph: "Delete to Beginning of Paragraph",
142
+ deleteToEndOfParagraph: "Delete to End of Paragraph",
143
+
144
+ // Selection
145
+ selectWord: "Select Word",
146
+ selectLine: "Select Line",
147
+ selectParagraph: "Select Paragraph",
148
+ selectToMark: "Select to Mark",
149
+ setMark: "Set Mark",
150
+ swapWithMark: "Swap with Mark",
151
+ deleteToMark: "Delete to Mark",
152
+
153
+ // Text transformation
154
+ capitalizeWord: "Capitalize Word",
155
+ uppercaseWord: "Uppercase Word",
156
+ lowercaseWord: "Lowercase Word",
157
+ transpose: "Transpose",
158
+ transposeWords: "Transpose Words",
159
+
160
+ // Insertion
161
+ insertNewline: "Insert Newline",
162
+ insertLineBreak: "Insert Line Break",
163
+ insertParagraphSeparator: "Insert Paragraph Separator",
164
+ insertTab: "Insert Tab",
165
+ insertBacktab: "Insert Backtab",
166
+ insertTabIgnoringFieldEditor: "Insert Tab Ignoring Field Editor",
167
+ insertNewlineIgnoringFieldEditor: "Insert Newline Ignoring Field Editor",
168
+
169
+ // Kill ring (Emacs-style)
170
+ yank: "Yank",
171
+ yankAndSelect: "Yank and Select",
172
+
173
+ // Completion
174
+ complete: "Complete",
175
+ cancelOperation: "Cancel Operation",
176
+
177
+ // Indentation
178
+ indent: "Indent",
179
+ };
180
+
181
+ export type MenuRole = keyof typeof roleLabelMap;
@@ -0,0 +1,22 @@
1
+ import SparkBunEvent from "./event";
2
+
3
+ type MenuClickedData = { id?: number; action: string; data?: unknown };
4
+ type OpenUrlData = { url: string };
5
+
6
+ export default {
7
+ applicationMenuClicked: (data: MenuClickedData) =>
8
+ new SparkBunEvent<MenuClickedData, { allow: boolean }>(
9
+ "application-menu-clicked",
10
+ data,
11
+ ),
12
+ contextMenuClicked: (data: MenuClickedData) =>
13
+ new SparkBunEvent<MenuClickedData, { allow: boolean }>(
14
+ "context-menu-clicked",
15
+ data,
16
+ ),
17
+ openUrl: (data: OpenUrlData) =>
18
+ new SparkBunEvent<OpenUrlData, void>("open-url", data),
19
+ reopen: (data: {}) => new SparkBunEvent<{}, void>("reopen", data),
20
+ beforeQuit: (data: {}) =>
21
+ new SparkBunEvent<{}, { allow: boolean }>("before-quit", data),
22
+ };
@@ -0,0 +1,27 @@
1
+ export default class SparkBunEvent<DataType, ResponseType> {
2
+ name: string;
3
+ data: DataType;
4
+ _response: ResponseType | undefined;
5
+ responseWasSet: boolean = false;
6
+
7
+ constructor(name: string, data: DataType) {
8
+ this.name = name;
9
+ this.data = data;
10
+ }
11
+
12
+ // Getter for response
13
+ get response(): ResponseType | undefined {
14
+ return this._response;
15
+ }
16
+
17
+ // Setter for response
18
+ set response(value: ResponseType) {
19
+ this._response = value;
20
+ this.responseWasSet = true; // Update flag when response is set
21
+ }
22
+
23
+ clearResponse() {
24
+ this._response = undefined;
25
+ this.responseWasSet = false;
26
+ }
27
+ }
@@ -0,0 +1,45 @@
1
+ import EventEmitter from "events";
2
+ import windowEvents from "./windowEvents";
3
+ import webviewEvents from "./webviewEvents";
4
+ import trayEvents from "./trayEvents";
5
+ import applicationEvents from "./ApplicationEvents";
6
+ import SparkBunEvent from "./event";
7
+
8
+ class SparkBunEventEmitter extends EventEmitter {
9
+ constructor() {
10
+ super();
11
+ }
12
+
13
+ // optionally pass in a specifier to make the event name specific.
14
+ // eg: will-navigate is listened to globally for all webviews, but
15
+ // will-navigate-1 is listened to for a specific webview with id 1
16
+ emitEvent(
17
+ SparkBunEvent: SparkBunEvent<any, any>,
18
+ specifier?: number | string,
19
+ ) {
20
+ if (specifier) {
21
+ this.emit(`${SparkBunEvent.name}-${specifier}`, SparkBunEvent);
22
+ } else {
23
+ this.emit(SparkBunEvent.name, SparkBunEvent);
24
+ }
25
+ }
26
+
27
+ events = {
28
+ window: {
29
+ ...windowEvents,
30
+ },
31
+ webview: {
32
+ ...webviewEvents,
33
+ },
34
+ tray: {
35
+ ...trayEvents,
36
+ },
37
+ app: {
38
+ ...applicationEvents,
39
+ },
40
+ };
41
+ }
42
+
43
+ export const sparkBunEventEmitter = new SparkBunEventEmitter();
44
+
45
+ export default sparkBunEventEmitter;
@@ -0,0 +1,11 @@
1
+ import SparkBunEvent from "./event";
2
+
3
+ type TrayClickedData = { id: number; action: string; data?: unknown };
4
+
5
+ export default {
6
+ trayClicked: (data: TrayClickedData) =>
7
+ new SparkBunEvent<TrayClickedData, { allow: boolean }>(
8
+ "tray-clicked",
9
+ data,
10
+ ),
11
+ };
@@ -0,0 +1,39 @@
1
+ import SparkBunEvent from "./event";
2
+
3
+ type DetailData = { detail: string };
4
+ type NewWindowOpenData = {
5
+ detail:
6
+ | string
7
+ | {
8
+ url: string;
9
+ isCmdClick: boolean;
10
+ modifierFlags?: number;
11
+ targetDisposition?: number;
12
+ userGesture?: boolean;
13
+ };
14
+ };
15
+
16
+ export default {
17
+ willNavigate: (data: DetailData) =>
18
+ new SparkBunEvent<DetailData, {}>("will-navigate", data),
19
+ didNavigate: (data: DetailData) =>
20
+ new SparkBunEvent<DetailData, {}>("did-navigate", data),
21
+ didNavigateInPage: (data: DetailData) =>
22
+ new SparkBunEvent<DetailData, {}>("did-navigate-in-page", data),
23
+ didCommitNavigation: (data: DetailData) =>
24
+ new SparkBunEvent<DetailData, {}>("did-commit-navigation", data),
25
+ domReady: (data: DetailData) =>
26
+ new SparkBunEvent<DetailData, {}>("dom-ready", data),
27
+ newWindowOpen: (data: NewWindowOpenData) =>
28
+ new SparkBunEvent<NewWindowOpenData, {}>("new-window-open", data),
29
+ hostMessage: (data: DetailData) =>
30
+ new SparkBunEvent<DetailData, {}>("host-message", data),
31
+ downloadStarted: (data: DetailData) =>
32
+ new SparkBunEvent<DetailData, {}>("download-started", data),
33
+ downloadProgress: (data: DetailData) =>
34
+ new SparkBunEvent<DetailData, {}>("download-progress", data),
35
+ downloadCompleted: (data: DetailData) =>
36
+ new SparkBunEvent<DetailData, {}>("download-completed", data),
37
+ downloadFailed: (data: DetailData) =>
38
+ new SparkBunEvent<DetailData, {}>("download-failed", data),
39
+ };
@@ -0,0 +1,23 @@
1
+ import SparkBunEvent from "./event";
2
+
3
+ type IdData = { id: number };
4
+ type ResizeData = {
5
+ id: number;
6
+ x: number;
7
+ y: number;
8
+ width: number;
9
+ height: number;
10
+ };
11
+ type MoveData = { id: number; x: number; y: number };
12
+ type KeyData = { id: number; keyCode: number; modifiers: number; isRepeat: boolean };
13
+
14
+ export default {
15
+ close: (data: IdData) => new SparkBunEvent<IdData, {}>("close", data),
16
+ resize: (data: ResizeData) =>
17
+ new SparkBunEvent<ResizeData, {}>("resize", data),
18
+ move: (data: MoveData) => new SparkBunEvent<MoveData, {}>("move", data),
19
+ focus: (data: IdData) => new SparkBunEvent<IdData, {}>("focus", data),
20
+ blur: (data: IdData) => new SparkBunEvent<IdData, {}>("blur", data),
21
+ keyDown: (data: KeyData) => new SparkBunEvent<KeyData, {}>("keyDown", data),
22
+ keyUp: (data: KeyData) => new SparkBunEvent<KeyData, {}>("keyUp", data),
23
+ };
@@ -0,0 +1,120 @@
1
+ import electobunEventEmmitter from "./events/eventEmitter";
2
+ import { BrowserWindow, type WindowOptionsType } from "./core/BrowserWindow";
3
+ import { BrowserView, type BrowserViewOptions } from "./core/BrowserView";
4
+ import { GpuWindow, type GpuWindowOptionsType } from "./core/GpuWindow";
5
+ import { WGPUView, type WGPUViewOptions } from "./core/WGPUView";
6
+ import { Tray, type TrayOptions } from "./core/Tray";
7
+ import * as ApplicationMenu from "./core/ApplicationMenu";
8
+ import * as ContextMenu from "./core/ContextMenu";
9
+ import {
10
+ Updater,
11
+ type UpdateStatusType,
12
+ type UpdateStatusEntry,
13
+ type UpdateStatusDetails,
14
+ } from "./core/Updater";
15
+ import * as Utils from "./core/Utils";
16
+ import type {
17
+ MessageBoxOptions,
18
+ MessageBoxResponse,
19
+ NotificationOptions,
20
+ } from "./core/Utils";
21
+ import {
22
+ type RPCSchema,
23
+ type SparkBunRPCSchema,
24
+ createRPC,
25
+ defineSparkBunRPC,
26
+ } from "../shared/rpc.js";
27
+ import type SparkBunEvent from "./events/event";
28
+ import * as PATHS from "./core/Paths";
29
+ import * as Socket from "./core/Socket";
30
+ import WGPU from "./webGPU";
31
+ import webgpu from "./webgpuAdapter";
32
+ import type { SparkBunConfig } from "./SparkBunConfig";
33
+ import { GlobalShortcut, Screen, Session, WGPUBridge } from "./proc/native";
34
+ import type {
35
+ Display,
36
+ Rectangle,
37
+ Point,
38
+ Cookie,
39
+ CookieFilter,
40
+ StorageType,
41
+ MenuItemConfig,
42
+ ApplicationMenuItemConfig,
43
+ } from "./proc/native";
44
+ import { BuildConfig, type BuildConfigType } from "./core/BuildConfig";
45
+
46
+ // Named Exports
47
+ export {
48
+ type RPCSchema,
49
+ type SparkBunRPCSchema,
50
+ type SparkBunEvent,
51
+ type SparkBunConfig,
52
+ type BuildConfigType,
53
+ type WindowOptionsType,
54
+ type BrowserViewOptions,
55
+ type GpuWindowOptionsType,
56
+ type WGPUViewOptions,
57
+ type TrayOptions,
58
+ type MessageBoxOptions,
59
+ type MessageBoxResponse,
60
+ type NotificationOptions,
61
+ type MenuItemConfig,
62
+ type ApplicationMenuItemConfig,
63
+ type Display,
64
+ type Rectangle,
65
+ type Point,
66
+ type Cookie,
67
+ type CookieFilter,
68
+ type StorageType,
69
+ type UpdateStatusType,
70
+ type UpdateStatusEntry,
71
+ type UpdateStatusDetails,
72
+ createRPC,
73
+ defineSparkBunRPC,
74
+ BrowserWindow,
75
+ BrowserView,
76
+ GpuWindow,
77
+ WGPUView,
78
+ Tray,
79
+ Updater,
80
+ Utils,
81
+ ApplicationMenu,
82
+ ContextMenu,
83
+ PATHS,
84
+ Socket,
85
+ WGPU,
86
+ webgpu,
87
+ GlobalShortcut,
88
+ Screen,
89
+ Session,
90
+ WGPUBridge,
91
+
92
+ BuildConfig,
93
+ };
94
+
95
+ // Default Export
96
+ const SparkBun = {
97
+ BrowserWindow,
98
+ BrowserView,
99
+ GpuWindow,
100
+ WGPUView,
101
+ Tray,
102
+ Updater,
103
+ Utils,
104
+ ApplicationMenu,
105
+ ContextMenu,
106
+ GlobalShortcut,
107
+ Screen,
108
+ Session,
109
+ WGPUBridge,
110
+
111
+ BuildConfig,
112
+ events: electobunEventEmmitter,
113
+ PATHS,
114
+ Socket,
115
+ WGPU,
116
+ webgpu,
117
+ };
118
+
119
+ // SparkBun
120
+ export default SparkBun;
@@ -0,0 +1,2 @@
1
+ export const preloadScript = "(function(){// src/bun/preload/encryption.ts\nfunction base64ToUint8Array(base64) {\n return new Uint8Array(atob(base64).split(\"\").map((char) => char.charCodeAt(0)));\n}\nfunction uint8ArrayToBase64(uint8Array) {\n let binary = \"\";\n for (let i = 0;i < uint8Array.length; i++) {\n binary += String.fromCharCode(uint8Array[i]);\n }\n return btoa(binary);\n}\nasync function generateKeyFromBytes(rawKey) {\n return await window.crypto.subtle.importKey(\"raw\", rawKey, { name: \"AES-GCM\" }, true, [\"encrypt\", \"decrypt\"]);\n}\nasync function initEncryption() {\n const secretKey = await generateKeyFromBytes(new Uint8Array(window.__sparkbunSecretKeyBytes));\n const encryptString = async (plaintext) => {\n const encoder = new TextEncoder;\n const encodedText = encoder.encode(plaintext);\n const iv = window.crypto.getRandomValues(new Uint8Array(12));\n const encryptedBuffer = await window.crypto.subtle.encrypt({ name: \"AES-GCM\", iv }, secretKey, encodedText);\n const encryptedData = new Uint8Array(encryptedBuffer.slice(0, -16));\n const tag = new Uint8Array(encryptedBuffer.slice(-16));\n return {\n encryptedData: uint8ArrayToBase64(encryptedData),\n iv: uint8ArrayToBase64(iv),\n tag: uint8ArrayToBase64(tag)\n };\n };\n const decryptString = async (encryptedDataB64, ivB64, tagB64) => {\n const encryptedData = base64ToUint8Array(encryptedDataB64);\n const iv = base64ToUint8Array(ivB64);\n const tag = base64ToUint8Array(tagB64);\n const combinedData = new Uint8Array(encryptedData.length + tag.length);\n combinedData.set(encryptedData);\n combinedData.set(tag, encryptedData.length);\n const decryptedBuffer = await window.crypto.subtle.decrypt({ name: \"AES-GCM\", iv }, secretKey, combinedData);\n const decoder = new TextDecoder;\n return decoder.decode(decryptedBuffer);\n };\n window.__sparkbun_encrypt = encryptString;\n window.__sparkbun_decrypt = decryptString;\n}\n\n// src/bun/preload/internalRpc.ts\nvar pendingRequests = {};\nvar requestId = 0;\nvar isProcessingQueue = false;\nvar sendQueue = [];\nfunction processQueue() {\n if (isProcessingQueue) {\n setTimeout(processQueue);\n return;\n }\n if (sendQueue.length === 0)\n return;\n isProcessingQueue = true;\n const batch = JSON.stringify(sendQueue);\n sendQueue.length = 0;\n window.__sparkbunInternalBridge?.postMessage(batch);\n setTimeout(() => {\n isProcessingQueue = false;\n }, 2);\n}\nfunction send(type, payload) {\n sendQueue.push(JSON.stringify({ type: \"message\", id: type, payload }));\n processQueue();\n}\nfunction request(type, payload) {\n return new Promise((resolve, reject) => {\n const id = `req_${++requestId}_${Date.now()}`;\n pendingRequests[id] = { resolve, reject };\n sendQueue.push(JSON.stringify({\n type: \"request\",\n method: type,\n id,\n params: payload,\n hostWebviewId: window.__sparkbunWebviewId\n }));\n processQueue();\n setTimeout(() => {\n if (pendingRequests[id]) {\n delete pendingRequests[id];\n reject(new Error(`Request timeout: ${type}`));\n }\n }, 1e4);\n });\n}\nfunction handleResponse(msg) {\n if (msg && msg.type === \"response\" && msg.id) {\n const pending = pendingRequests[msg.id];\n if (pending) {\n delete pendingRequests[msg.id];\n if (msg.success)\n pending.resolve(msg.payload);\n else\n pending.reject(msg.payload);\n }\n }\n}\n\n// src/bun/preload/dragRegions.ts\nfunction isAppRegionDrag(e) {\n const target = e.target;\n if (!target || !target.closest)\n return false;\n if (target.closest(\".sparkbun-webkit-app-region-no-drag\") || target.closest('[style*=\"app-region\"][style*=\"no-drag\"]')) {\n return false;\n }\n const draggableByStyle = target.closest('[style*=\"app-region\"][style*=\"drag\"]');\n const draggableByClass = target.closest(\".sparkbun-webkit-app-region-drag\");\n return !!(draggableByStyle || draggableByClass);\n}\nfunction initDragRegions() {\n document.addEventListener(\"mousedown\", (e) => {\n if (isAppRegionDrag(e)) {\n send(\"startWindowMove\", { id: window.__sparkbunWindowId });\n }\n });\n document.addEventListener(\"mouseup\", (e) => {\n if (isAppRegionDrag(e)) {\n send(\"stopWindowMove\", { id: window.__sparkbunWindowId });\n }\n });\n}\n\n// src/bun/preload/overlaySync.ts\nclass OverlaySyncController {\n element;\n options;\n lastRect = { x: 0, y: 0, width: 0, height: 0 };\n resizeObserver = null;\n positionLoop = null;\n resizeHandler = null;\n burstUntil = 0;\n constructor(element, options) {\n this.element = element;\n this.options = {\n onSync: options.onSync,\n getMasks: options.getMasks ?? (() => []),\n burstIntervalMs: options.burstIntervalMs ?? 50,\n baseIntervalMs: options.baseIntervalMs ?? 100,\n burstDurationMs: options.burstDurationMs ?? 500\n };\n }\n start() {\n this.resizeObserver = new ResizeObserver(() => this.sync());\n this.resizeObserver.observe(this.element);\n const loop = () => {\n this.sync();\n const now = performance.now();\n const interval = now < this.burstUntil ? this.options.burstIntervalMs : this.options.baseIntervalMs;\n this.positionLoop = setTimeout(loop, interval);\n };\n this.positionLoop = setTimeout(loop, this.options.baseIntervalMs);\n this.resizeHandler = () => this.sync(true);\n window.addEventListener(\"resize\", this.resizeHandler);\n }\n stop() {\n if (this.resizeObserver)\n this.resizeObserver.disconnect();\n if (this.positionLoop)\n clearTimeout(this.positionLoop);\n if (this.resizeHandler) {\n window.removeEventListener(\"resize\", this.resizeHandler);\n }\n this.resizeObserver = null;\n this.positionLoop = null;\n this.resizeHandler = null;\n }\n forceSync() {\n this.sync(true);\n }\n setLastRect(rect) {\n this.lastRect = rect;\n }\n sync(force = false) {\n const rect = this.element.getBoundingClientRect();\n const newRect = {\n x: rect.x,\n y: rect.y,\n width: rect.width,\n height: rect.height\n };\n if (newRect.width === 0 && newRect.height === 0) {\n return;\n }\n if (!force && newRect.x === this.lastRect.x && newRect.y === this.lastRect.y && newRect.width === this.lastRect.width && newRect.height === this.lastRect.height) {\n return;\n }\n this.burstUntil = performance.now() + this.options.burstDurationMs;\n this.lastRect = newRect;\n const masks = this.options.getMasks();\n this.options.onSync(newRect, JSON.stringify(masks));\n }\n}\n\n// src/bun/preload/webviewTag.ts\nvar webviewRegistry = {};\n\nclass SparkBunWebviewTag extends HTMLElement {\n webviewId = null;\n maskSelectors = new Set;\n _sync = null;\n transparent = false;\n passthroughEnabled = false;\n hidden = false;\n sandboxed = false;\n _eventListeners = {};\n static get observedAttributes() {\n return [\"src\", \"html\"];\n }\n constructor() {\n super();\n }\n connectedCallback() {\n requestAnimationFrame(() => this.initWebview());\n }\n attributeChangedCallback(name, oldValue, newValue) {\n if (oldValue === newValue)\n return;\n if (newValue === null)\n return;\n if (this.webviewId === null)\n return;\n if (name === \"src\")\n this.loadURL(newValue);\n else if (name === \"html\")\n this.loadHTML(newValue);\n }\n disconnectedCallback() {\n if (this.webviewId !== null) {\n send(\"webviewTagRemove\", { id: this.webviewId });\n delete webviewRegistry[this.webviewId];\n }\n if (this._sync)\n this._sync.stop();\n }\n getInitialNavigationRules() {\n const rawRules = this.getAttribute(\"navigation-rules\");\n if (rawRules === null) {\n return null;\n }\n const trimmed = rawRules.trim();\n if (!trimmed) {\n return [];\n }\n try {\n const parsed = JSON.parse(trimmed);\n if (!Array.isArray(parsed) || !parsed.every((rule) => typeof rule === \"string\")) {\n throw new Error(\"navigation-rules must be a JSON string array\");\n }\n return parsed;\n } catch (error) {\n console.error(\"Invalid navigation-rules attribute:\", error);\n return [];\n }\n }\n async initWebview() {\n const rect = this.getBoundingClientRect();\n const initialRect = {\n x: rect.x,\n y: rect.y,\n width: rect.width,\n height: rect.height\n };\n const url = this.getAttribute(\"src\");\n const html = this.getAttribute(\"html\");\n const preload = this.getAttribute(\"preload\");\n const partition = this.getAttribute(\"partition\");\n const renderer = this.getAttribute(\"renderer\") || \"native\";\n const masks = this.getAttribute(\"masks\");\n const navigationRules = this.getInitialNavigationRules();\n const sandbox = this.hasAttribute(\"sandbox\");\n this.sandboxed = sandbox;\n const transparent = this.hasAttribute(\"transparent\");\n const passthrough = this.hasAttribute(\"passthrough\");\n this.transparent = transparent;\n this.passthroughEnabled = passthrough;\n if (transparent)\n this.style.opacity = \"0\";\n if (passthrough)\n this.style.pointerEvents = \"none\";\n if (masks) {\n masks.split(\",\").forEach((s) => this.maskSelectors.add(s.trim()));\n }\n try {\n const webviewInitParams = {\n hostWebviewId: window.__sparkbunWebviewId,\n windowId: window.__sparkbunWindowId,\n renderer,\n url,\n html,\n preload,\n partition,\n frame: {\n width: rect.width,\n height: rect.height,\n x: rect.x,\n y: rect.y\n },\n sandbox,\n transparent,\n passthrough,\n ...navigationRules === null ? {} : { navigationRules }\n };\n const webviewId = await request(\"webviewTagInit\", webviewInitParams);\n this.webviewId = webviewId;\n this.id = `sparkbun-webview-${webviewId}`;\n webviewRegistry[webviewId] = this;\n this.setupObservers(initialRect);\n this.syncDimensions(true);\n requestAnimationFrame(() => {\n Object.values(webviewRegistry).forEach((webview) => {\n if (webview !== this && webview.webviewId !== null) {\n webview.syncDimensions(true);\n }\n });\n });\n } catch (err) {\n console.error(\"Failed to init webview:\", err);\n }\n }\n setupObservers(initialRect) {\n const getMasks = () => {\n const rect = this.getBoundingClientRect();\n const masks = [];\n this.maskSelectors.forEach((selector) => {\n try {\n document.querySelectorAll(selector).forEach((el) => {\n const mr = el.getBoundingClientRect();\n masks.push({\n x: mr.x - rect.x,\n y: mr.y - rect.y,\n width: mr.width,\n height: mr.height\n });\n });\n } catch (_e) {}\n });\n return masks;\n };\n this._sync = new OverlaySyncController(this, {\n onSync: (rect, masksJson) => {\n if (this.webviewId === null)\n return;\n send(\"webviewTagResize\", {\n id: this.webviewId,\n frame: rect,\n masks: masksJson\n });\n },\n getMasks,\n burstIntervalMs: 10,\n baseIntervalMs: 100,\n burstDurationMs: 50\n });\n this._sync.setLastRect(initialRect);\n this._sync.start();\n }\n syncDimensions(force = false) {\n if (!this._sync)\n return;\n if (force) {\n this._sync.forceSync();\n }\n }\n loadURL(url) {\n if (this.webviewId === null)\n return;\n this.setAttribute(\"src\", url);\n send(\"webviewTagUpdateSrc\", { id: this.webviewId, url });\n }\n loadHTML(html) {\n if (this.webviewId === null)\n return;\n send(\"webviewTagUpdateHtml\", { id: this.webviewId, html });\n }\n reload() {\n if (this.webviewId !== null)\n send(\"webviewTagReload\", { id: this.webviewId });\n }\n goBack() {\n if (this.webviewId !== null)\n send(\"webviewTagGoBack\", { id: this.webviewId });\n }\n goForward() {\n if (this.webviewId !== null)\n send(\"webviewTagGoForward\", { id: this.webviewId });\n }\n async canGoBack() {\n if (this.webviewId === null)\n return false;\n return await request(\"webviewTagCanGoBack\", {\n id: this.webviewId\n });\n }\n async canGoForward() {\n if (this.webviewId === null)\n return false;\n return await request(\"webviewTagCanGoForward\", {\n id: this.webviewId\n });\n }\n toggleTransparent(value) {\n if (this.webviewId === null)\n return;\n this.transparent = value !== undefined ? value : !this.transparent;\n this.style.opacity = this.transparent ? \"0\" : \"\";\n send(\"webviewTagSetTransparent\", {\n id: this.webviewId,\n transparent: this.transparent\n });\n }\n togglePassthrough(value) {\n if (this.webviewId === null)\n return;\n this.passthroughEnabled = value !== undefined ? value : !this.passthroughEnabled;\n this.style.pointerEvents = this.passthroughEnabled ? \"none\" : \"\";\n send(\"webviewTagSetPassthrough\", {\n id: this.webviewId,\n enablePassthrough: this.passthroughEnabled\n });\n }\n toggleHidden(value) {\n if (this.webviewId === null)\n return;\n this.hidden = value !== undefined ? value : !this.hidden;\n send(\"webviewTagSetHidden\", { id: this.webviewId, hidden: this.hidden });\n }\n addMaskSelector(selector) {\n this.maskSelectors.add(selector);\n this.syncDimensions(true);\n }\n removeMaskSelector(selector) {\n this.maskSelectors.delete(selector);\n this.syncDimensions(true);\n }\n setNavigationRules(rules) {\n if (this.webviewId !== null) {\n send(\"webviewTagSetNavigationRules\", { id: this.webviewId, rules });\n }\n }\n findInPage(searchText, options) {\n if (this.webviewId === null)\n return;\n const forward = options?.forward !== false;\n const matchCase = options?.matchCase || false;\n send(\"webviewTagFindInPage\", {\n id: this.webviewId,\n searchText,\n forward,\n matchCase\n });\n }\n stopFindInPage() {\n if (this.webviewId !== null)\n send(\"webviewTagStopFind\", { id: this.webviewId });\n }\n openDevTools() {\n if (this.webviewId !== null)\n send(\"webviewTagOpenDevTools\", { id: this.webviewId });\n }\n closeDevTools() {\n if (this.webviewId !== null)\n send(\"webviewTagCloseDevTools\", { id: this.webviewId });\n }\n toggleDevTools() {\n if (this.webviewId !== null)\n send(\"webviewTagToggleDevTools\", { id: this.webviewId });\n }\n executeJavascript(js) {\n if (this.webviewId === null)\n return;\n send(\"webviewTagExecuteJavascript\", { id: this.webviewId, js });\n }\n on(event, listener) {\n if (!this._eventListeners[event])\n this._eventListeners[event] = [];\n this._eventListeners[event].push(listener);\n }\n off(event, listener) {\n if (!this._eventListeners[event])\n return;\n const idx = this._eventListeners[event].indexOf(listener);\n if (idx !== -1)\n this._eventListeners[event].splice(idx, 1);\n }\n emit(event, detail) {\n const listeners = this._eventListeners[event];\n if (listeners) {\n const customEvent = new CustomEvent(event, { detail });\n listeners.forEach((fn) => fn(customEvent));\n }\n }\n get src() {\n return this.getAttribute(\"src\");\n }\n set src(value) {\n if (value) {\n this.setAttribute(\"src\", value);\n } else {\n this.removeAttribute(\"src\");\n }\n }\n get html() {\n return this.getAttribute(\"html\");\n }\n set html(value) {\n if (value) {\n this.setAttribute(\"html\", value);\n } else {\n this.removeAttribute(\"html\");\n }\n }\n get preload() {\n return this.getAttribute(\"preload\");\n }\n set preload(value) {\n if (value)\n this.setAttribute(\"preload\", value);\n else\n this.removeAttribute(\"preload\");\n }\n get renderer() {\n return this.getAttribute(\"renderer\") || \"native\";\n }\n set renderer(value) {\n this.setAttribute(\"renderer\", value);\n }\n get sandbox() {\n return this.sandboxed;\n }\n}\nfunction initWebviewTag() {\n if (!customElements.get(\"sparkbun-webview\")) {\n customElements.define(\"sparkbun-webview\", SparkBunWebviewTag);\n }\n const injectStyles = () => {\n const style = document.createElement(\"style\");\n style.textContent = `\nsparkbun-webview {\n\tdisplay: block;\n\twidth: 800px;\n\theight: 300px;\n\tbackground: #fff;\n\tbackground-repeat: no-repeat !important;\n\toverflow: hidden;\n}\n`;\n if (document.head?.firstChild) {\n document.head.insertBefore(style, document.head.firstChild);\n } else if (document.head) {\n document.head.appendChild(style);\n }\n };\n if (document.head) {\n injectStyles();\n } else {\n document.addEventListener(\"DOMContentLoaded\", injectStyles);\n }\n}\n\n// src/bun/preload/wgpuTag.ts\nvar wgpuTagRegistry = {};\n\nclass SparkBunWgpuTag extends HTMLElement {\n wgpuViewId = null;\n maskSelectors = new Set;\n _sync = null;\n transparent = false;\n passthroughEnabled = false;\n hidden = false;\n _eventListeners = {};\n constructor() {\n super();\n }\n connectedCallback() {\n requestAnimationFrame(() => this.initWgpuView());\n }\n disconnectedCallback() {\n if (this.wgpuViewId !== null) {\n send(\"wgpuTagRemove\", { id: this.wgpuViewId });\n delete wgpuTagRegistry[this.wgpuViewId];\n }\n if (this._sync)\n this._sync.stop();\n }\n async initWgpuView() {\n const rect = this.getBoundingClientRect();\n const initialRect = {\n x: rect.x,\n y: rect.y,\n width: rect.width,\n height: rect.height\n };\n const transparent = this.hasAttribute(\"transparent\");\n const passthrough = this.hasAttribute(\"passthrough\");\n const hidden = this.hasAttribute(\"hidden\");\n const masks = this.getAttribute(\"masks\");\n this.transparent = transparent;\n this.passthroughEnabled = passthrough;\n this.hidden = hidden;\n if (masks) {\n masks.split(\",\").forEach((s) => this.maskSelectors.add(s.trim()));\n }\n if (transparent)\n this.style.opacity = \"0\";\n if (passthrough)\n this.style.pointerEvents = \"none\";\n try {\n const wgpuViewId = await request(\"wgpuTagInit\", {\n windowId: window.__sparkbunWindowId,\n frame: {\n width: rect.width,\n height: rect.height,\n x: rect.x,\n y: rect.y\n },\n transparent,\n passthrough\n });\n this.wgpuViewId = wgpuViewId;\n this.id = `sparkbun-wgpu-${wgpuViewId}`;\n wgpuTagRegistry[wgpuViewId] = this;\n this.setupObservers(initialRect);\n this.syncDimensions(true);\n if (hidden) {\n this.toggleHidden(true);\n }\n requestAnimationFrame(() => {\n Object.values(wgpuTagRegistry).forEach((view) => {\n if (view !== this && view.wgpuViewId !== null) {\n view.syncDimensions(true);\n }\n });\n });\n this.emit(\"ready\", { id: wgpuViewId });\n } catch (err) {\n console.error(\"Failed to init WGPU view:\", err);\n }\n }\n setupObservers(initialRect) {\n const getMasks = () => {\n const rect = this.getBoundingClientRect();\n const masks = [];\n this.maskSelectors.forEach((selector) => {\n try {\n document.querySelectorAll(selector).forEach((el) => {\n const mr = el.getBoundingClientRect();\n masks.push({\n x: mr.x - rect.x,\n y: mr.y - rect.y,\n width: mr.width,\n height: mr.height\n });\n });\n } catch (_e) {}\n });\n return masks;\n };\n this._sync = new OverlaySyncController(this, {\n onSync: (rect, masksJson) => {\n if (this.wgpuViewId === null)\n return;\n send(\"wgpuTagResize\", {\n id: this.wgpuViewId,\n frame: rect,\n masks: masksJson\n });\n },\n getMasks,\n burstIntervalMs: 10,\n baseIntervalMs: 100,\n burstDurationMs: 50\n });\n this._sync.setLastRect(initialRect);\n this._sync.start();\n }\n syncDimensions(force = false) {\n if (!this._sync)\n return;\n if (force) {\n this._sync.forceSync();\n }\n }\n toggleTransparent(value) {\n if (this.wgpuViewId === null)\n return;\n this.transparent = value !== undefined ? value : !this.transparent;\n this.style.opacity = this.transparent ? \"0\" : \"\";\n send(\"wgpuTagSetTransparent\", {\n id: this.wgpuViewId,\n transparent: this.transparent\n });\n }\n togglePassthrough(value) {\n if (this.wgpuViewId === null)\n return;\n this.passthroughEnabled = value !== undefined ? value : !this.passthroughEnabled;\n this.style.pointerEvents = this.passthroughEnabled ? \"none\" : \"\";\n send(\"wgpuTagSetPassthrough\", {\n id: this.wgpuViewId,\n passthrough: this.passthroughEnabled\n });\n }\n toggleHidden(value) {\n if (this.wgpuViewId === null)\n return;\n this.hidden = value !== undefined ? value : !this.hidden;\n send(\"wgpuTagSetHidden\", { id: this.wgpuViewId, hidden: this.hidden });\n }\n runTest() {\n if (this.wgpuViewId === null)\n return;\n send(\"wgpuTagRunTest\", { id: this.wgpuViewId });\n }\n addMaskSelector(selector) {\n this.maskSelectors.add(selector);\n this.syncDimensions(true);\n }\n removeMaskSelector(selector) {\n this.maskSelectors.delete(selector);\n this.syncDimensions(true);\n }\n on(event, listener) {\n if (!this._eventListeners[event])\n this._eventListeners[event] = [];\n this._eventListeners[event].push(listener);\n }\n off(event, listener) {\n if (!this._eventListeners[event])\n return;\n const idx = this._eventListeners[event].indexOf(listener);\n if (idx !== -1)\n this._eventListeners[event].splice(idx, 1);\n }\n emit(event, detail) {\n const listeners = this._eventListeners[event];\n if (listeners) {\n const customEvent = new CustomEvent(event, { detail });\n listeners.forEach((fn) => fn(customEvent));\n }\n }\n}\nfunction initWgpuTag() {\n if (!customElements.get(\"sparkbun-wgpu\")) {\n customElements.define(\"sparkbun-wgpu\", SparkBunWgpuTag);\n }\n const injectStyles = () => {\n const style = document.createElement(\"style\");\n style.textContent = `\nsparkbun-wgpu {\n\tdisplay: block;\n\twidth: 800px;\n\theight: 300px;\n\tbackground: #000;\n\toverflow: hidden;\n}\n`;\n if (document.head?.firstChild) {\n document.head.insertBefore(style, document.head.firstChild);\n } else if (document.head) {\n document.head.appendChild(style);\n }\n };\n if (document.head) {\n injectStyles();\n } else {\n document.addEventListener(\"DOMContentLoaded\", injectStyles);\n }\n}\n\n// src/bun/preload/events.ts\nfunction emitWebviewEvent(eventName, detail) {\n setTimeout(() => {\n const bridge = window.__sparkbunEventBridge || window.__sparkbunInternalBridge;\n bridge?.postMessage(JSON.stringify({\n id: \"webviewEvent\",\n type: \"message\",\n payload: {\n id: window.__sparkbunWebviewId,\n eventName,\n detail\n }\n }));\n });\n}\nfunction initLifecycleEvents() {\n window.addEventListener(\"load\", () => {\n if (window === window.top) {\n emitWebviewEvent(\"dom-ready\", document.location.href);\n }\n });\n window.addEventListener(\"popstate\", () => {\n emitWebviewEvent(\"did-navigate-in-page\", window.location.href);\n });\n window.addEventListener(\"hashchange\", () => {\n emitWebviewEvent(\"did-navigate-in-page\", window.location.href);\n });\n}\nvar cmdKeyHeld = false;\nvar cmdKeyTimestamp = 0;\nvar CMD_KEY_THRESHOLD_MS = 500;\nfunction isCmdHeld() {\n if (cmdKeyHeld)\n return true;\n return Date.now() - cmdKeyTimestamp < CMD_KEY_THRESHOLD_MS && cmdKeyTimestamp > 0;\n}\nfunction initCmdClickHandling() {\n window.addEventListener(\"keydown\", (event) => {\n if (event.key === \"Meta\" || event.metaKey) {\n cmdKeyHeld = true;\n cmdKeyTimestamp = Date.now();\n }\n }, true);\n window.addEventListener(\"keyup\", (event) => {\n if (event.key === \"Meta\") {\n cmdKeyHeld = false;\n cmdKeyTimestamp = Date.now();\n }\n }, true);\n window.addEventListener(\"blur\", () => {\n cmdKeyHeld = false;\n });\n window.addEventListener(\"click\", (event) => {\n if (event.metaKey || event.ctrlKey) {\n const anchor = event.target?.closest?.(\"a\");\n if (anchor && anchor.href) {\n event.preventDefault();\n event.stopPropagation();\n event.stopImmediatePropagation();\n emitWebviewEvent(\"new-window-open\", JSON.stringify({\n url: anchor.href,\n isCmdClick: true,\n isSPANavigation: false\n }));\n }\n }\n }, true);\n}\nfunction initSPANavigationInterception() {\n const originalPushState = history.pushState;\n const originalReplaceState = history.replaceState;\n history.pushState = function(state, title, url) {\n if (isCmdHeld() && url) {\n const resolvedUrl = new URL(String(url), window.location.href).href;\n emitWebviewEvent(\"new-window-open\", JSON.stringify({\n url: resolvedUrl,\n isCmdClick: true,\n isSPANavigation: true\n }));\n return;\n }\n return originalPushState.apply(this, [state, title, url]);\n };\n history.replaceState = function(state, title, url) {\n if (isCmdHeld() && url) {\n const resolvedUrl = new URL(String(url), window.location.href).href;\n emitWebviewEvent(\"new-window-open\", JSON.stringify({\n url: resolvedUrl,\n isCmdClick: true,\n isSPANavigation: true\n }));\n return;\n }\n return originalReplaceState.apply(this, [state, title, url]);\n };\n}\nfunction initOverscrollPrevention() {\n document.addEventListener(\"DOMContentLoaded\", () => {\n const style = document.createElement(\"style\");\n style.type = \"text/css\";\n style.appendChild(document.createTextNode(\"html, body { overscroll-behavior: none; }\"));\n document.head.appendChild(style);\n });\n}\n\n// src/bun/preload/index.ts\ninitEncryption().catch((err) => console.error(\"Failed to initialize encryption:\", err));\nvar internalMessageHandler = (msg) => {\n handleResponse(msg);\n};\nvar defaultUserMessageHandler = (msg) => {\n if (!window.__sparkbunPendingHostMessages) {\n window.__sparkbunPendingHostMessages = [];\n }\n window.__sparkbunPendingHostMessages.push(msg);\n};\nif (!window.__sparkbun) {\n window.__sparkbun = {\n receiveInternalMessageFromHost: internalMessageHandler,\n receiveMessageFromHost: defaultUserMessageHandler,\n receiveInternalMessageFromBun: internalMessageHandler,\n receiveMessageFromBun: defaultUserMessageHandler\n };\n} else {\n window.__sparkbun.receiveInternalMessageFromHost = internalMessageHandler;\n window.__sparkbun.receiveMessageFromHost = defaultUserMessageHandler;\n window.__sparkbun.receiveInternalMessageFromBun = internalMessageHandler;\n window.__sparkbun.receiveMessageFromBun = defaultUserMessageHandler;\n}\nwindow.__sparkbunSendToHost = (message) => {\n emitWebviewEvent(\"host-message\", JSON.stringify(message));\n};\ninitLifecycleEvents();\ninitCmdClickHandling();\ninitSPANavigationInterception();\ninitOverscrollPrevention();\ninitDragRegions();\ninitWebviewTag();\ninitWgpuTag();\n})();";
2
+ export const preloadScriptSandboxed = "(function(){// src/bun/preload/events.ts\nfunction emitWebviewEvent(eventName, detail) {\n setTimeout(() => {\n const bridge = window.__sparkbunEventBridge || window.__sparkbunInternalBridge;\n bridge?.postMessage(JSON.stringify({\n id: \"webviewEvent\",\n type: \"message\",\n payload: {\n id: window.__sparkbunWebviewId,\n eventName,\n detail\n }\n }));\n });\n}\nfunction initLifecycleEvents() {\n window.addEventListener(\"load\", () => {\n if (window === window.top) {\n emitWebviewEvent(\"dom-ready\", document.location.href);\n }\n });\n window.addEventListener(\"popstate\", () => {\n emitWebviewEvent(\"did-navigate-in-page\", window.location.href);\n });\n window.addEventListener(\"hashchange\", () => {\n emitWebviewEvent(\"did-navigate-in-page\", window.location.href);\n });\n}\nvar cmdKeyHeld = false;\nvar cmdKeyTimestamp = 0;\nvar CMD_KEY_THRESHOLD_MS = 500;\nfunction isCmdHeld() {\n if (cmdKeyHeld)\n return true;\n return Date.now() - cmdKeyTimestamp < CMD_KEY_THRESHOLD_MS && cmdKeyTimestamp > 0;\n}\nfunction initCmdClickHandling() {\n window.addEventListener(\"keydown\", (event) => {\n if (event.key === \"Meta\" || event.metaKey) {\n cmdKeyHeld = true;\n cmdKeyTimestamp = Date.now();\n }\n }, true);\n window.addEventListener(\"keyup\", (event) => {\n if (event.key === \"Meta\") {\n cmdKeyHeld = false;\n cmdKeyTimestamp = Date.now();\n }\n }, true);\n window.addEventListener(\"blur\", () => {\n cmdKeyHeld = false;\n });\n window.addEventListener(\"click\", (event) => {\n if (event.metaKey || event.ctrlKey) {\n const anchor = event.target?.closest?.(\"a\");\n if (anchor && anchor.href) {\n event.preventDefault();\n event.stopPropagation();\n event.stopImmediatePropagation();\n emitWebviewEvent(\"new-window-open\", JSON.stringify({\n url: anchor.href,\n isCmdClick: true,\n isSPANavigation: false\n }));\n }\n }\n }, true);\n}\nfunction initSPANavigationInterception() {\n const originalPushState = history.pushState;\n const originalReplaceState = history.replaceState;\n history.pushState = function(state, title, url) {\n if (isCmdHeld() && url) {\n const resolvedUrl = new URL(String(url), window.location.href).href;\n emitWebviewEvent(\"new-window-open\", JSON.stringify({\n url: resolvedUrl,\n isCmdClick: true,\n isSPANavigation: true\n }));\n return;\n }\n return originalPushState.apply(this, [state, title, url]);\n };\n history.replaceState = function(state, title, url) {\n if (isCmdHeld() && url) {\n const resolvedUrl = new URL(String(url), window.location.href).href;\n emitWebviewEvent(\"new-window-open\", JSON.stringify({\n url: resolvedUrl,\n isCmdClick: true,\n isSPANavigation: true\n }));\n return;\n }\n return originalReplaceState.apply(this, [state, title, url]);\n };\n}\nfunction initOverscrollPrevention() {\n document.addEventListener(\"DOMContentLoaded\", () => {\n const style = document.createElement(\"style\");\n style.type = \"text/css\";\n style.appendChild(document.createTextNode(\"html, body { overscroll-behavior: none; }\"));\n document.head.appendChild(style);\n });\n}\n\n// src/bun/preload/index-sandboxed.ts\ninitLifecycleEvents();\ninitCmdClickHandling();\ninitSPANavigationInterception();\ninitOverscrollPrevention();\n})();";
@@ -0,0 +1,65 @@
1
+ // Standalone build script for the preload.
2
+ // Normally this is run as part of "bun build.ts", but you can run this directly:
3
+ // bun src/bun/preload/build.ts
4
+
5
+ import { join, dirname } from "path";
6
+ import { writeFileSync, mkdirSync } from "fs";
7
+
8
+ async function buildPreload() {
9
+ const preloadDir = dirname(import.meta.path);
10
+ const outputDir = join(preloadDir, ".generated");
11
+ const outputPath = join(outputDir, "compiled.ts");
12
+
13
+ mkdirSync(outputDir, { recursive: true });
14
+
15
+ // Build full preload (trusted webviews)
16
+ const fullPreloadEntry = join(preloadDir, "index.ts");
17
+ const fullResult = await Bun.build({
18
+ entrypoints: [fullPreloadEntry],
19
+ target: "browser",
20
+ format: "esm",
21
+ minify: false,
22
+ });
23
+
24
+ if (!fullResult.success) {
25
+ console.error("Full preload build failed:", fullResult.logs);
26
+ throw new Error("Failed to build full preload script");
27
+ }
28
+
29
+ // Build sandboxed preload (untrusted webviews)
30
+ const sandboxedPreloadEntry = join(preloadDir, "index-sandboxed.ts");
31
+ const sandboxedResult = await Bun.build({
32
+ entrypoints: [sandboxedPreloadEntry],
33
+ target: "browser",
34
+ format: "esm",
35
+ minify: false,
36
+ });
37
+
38
+ if (!sandboxedResult.success) {
39
+ console.error("Sandboxed preload build failed:", sandboxedResult.logs);
40
+ throw new Error("Failed to build sandboxed preload script");
41
+ }
42
+
43
+ // Bun does not currently support iife output, so we wrap the ESM bundle manually
44
+ // to keep preload globals scoped for script injection.
45
+ const fullPreloadJs = `(function(){${await fullResult.outputs[0]!.text()}})();`;
46
+ const sandboxedPreloadJs = `(function(){${await sandboxedResult.outputs[0]!.text()}})();`;
47
+
48
+ const outputContent = `// Auto-generated file. Do not edit directly.
49
+ // Run "bun build.ts" or "bun build:dev" from the package folder to regenerate.
50
+
51
+ // Full preload for trusted webviews (RPC, encryption, drag regions, webview tags)
52
+ export const preloadScript = ${JSON.stringify(fullPreloadJs)};
53
+
54
+ // Minimal preload for sandboxed/untrusted webviews (lifecycle events only, no RPC)
55
+ export const preloadScriptSandboxed = ${JSON.stringify(sandboxedPreloadJs)};
56
+ `;
57
+
58
+ writeFileSync(outputPath, outputContent);
59
+ console.log(`Preload scripts compiled to ${outputPath} (full + sandboxed)`);
60
+ }
61
+
62
+ buildPreload().catch((err) => {
63
+ console.error("Failed to build preload:", err);
64
+ process.exit(1);
65
+ });
@@ -0,0 +1,41 @@
1
+ // Drag Region Support for custom titlebars
2
+ // Detects elements with CSS app-region: drag or .sparkbun-webkit-app-region-drag class
3
+
4
+ import "./globals.d.ts";
5
+ import { send } from "./internalRpc";
6
+
7
+ function isAppRegionDrag(e: MouseEvent): boolean {
8
+ const target = e.target as HTMLElement;
9
+ if (!target || !target.closest) return false;
10
+
11
+ // If the target is inside a no-drag region, it should not trigger window move
12
+ if (
13
+ target.closest(".sparkbun-webkit-app-region-no-drag") ||
14
+ target.closest('[style*="app-region"][style*="no-drag"]')
15
+ ) {
16
+ return false;
17
+ }
18
+
19
+ // Check for inline style with app-region: drag
20
+ const draggableByStyle = target.closest(
21
+ '[style*="app-region"][style*="drag"]',
22
+ );
23
+ // Check for class-based drag region
24
+ const draggableByClass = target.closest(".sparkbun-webkit-app-region-drag");
25
+
26
+ return !!(draggableByStyle || draggableByClass);
27
+ }
28
+
29
+ export function initDragRegions() {
30
+ document.addEventListener("mousedown", (e) => {
31
+ if (isAppRegionDrag(e)) {
32
+ send("startWindowMove", { id: window.__sparkbunWindowId });
33
+ }
34
+ });
35
+
36
+ document.addEventListener("mouseup", (e) => {
37
+ if (isAppRegionDrag(e)) {
38
+ send("stopWindowMove", { id: window.__sparkbunWindowId });
39
+ }
40
+ });
41
+ }