electrobun 0.1.10 → 0.1.13-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -60,7 +60,7 @@ Ways to get involved at this early stage:
60
60
  - cmake
61
61
  - webkit2gtk and GTK development packages
62
62
 
63
- Un Ubuntu/Debian based distros: `sudo apt install build-essential cmake pkg-config libgtk-3-dev libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev`
63
+ On Ubuntu/Debian based distros: `sudo apt install build-essential cmake pkg-config libgtk-3-dev libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev`
64
64
 
65
65
  ### First-time Setup
66
66
 
@@ -46,11 +46,14 @@ const menuConfigWithDefaults = (
46
46
  if (item.type === "divider" || item.type === "separator") {
47
47
  return { type: "divider" };
48
48
  } else {
49
+ // Use shared serialization method
50
+ const actionWithDataId = ffi.internal.serializeMenuAction(item.action || "", item.data);
51
+
49
52
  return {
50
53
  label: item.label || roleLabelMap[item.role] || "",
51
54
  type: item.type || "normal",
52
55
  // application menus can either have an action or a role. not both.
53
- ...(item.role ? { role: item.role } : { action: item.action || "" }),
56
+ ...(item.role ? { role: item.role } : { action: actionWithDataId }),
54
57
  // default enabled to true unless explicitly set to false
55
58
  enabled: item.enabled === false ? false : true,
56
59
  checked: Boolean(item.checked),
@@ -48,11 +48,14 @@ const menuConfigWithDefaults = (
48
48
  if (item.type === "divider" || item.type === "separator") {
49
49
  return { type: "divider" };
50
50
  } else {
51
+ // Use shared serialization method
52
+ const actionWithDataId = ffi.internal.serializeMenuAction(item.action || "", item.data);
53
+
51
54
  return {
52
55
  label: item.label || roleLabelMap[item.role] || "",
53
56
  type: item.type || "normal",
54
57
  // application menus can either have an action or a role. not both.
55
- ...(item.role ? { role: item.role } : { action: item.action || "" }),
58
+ ...(item.role ? { role: item.role } : { action: actionWithDataId }),
56
59
  // default enabled to true unless explicitly set to false
57
60
  enabled: item.enabled === false ? false : true,
58
61
  checked: Boolean(item.checked),
@@ -85,6 +85,9 @@ const startRPCServer = () => {
85
85
  // Anything beyond the backpressure limit will be dropped
86
86
  backpressureLimit: payloadLimit * 2,
87
87
  open(ws) {
88
+ if (!ws?.data) {
89
+ return;
90
+ }
88
91
  const { webviewId } = ws.data;
89
92
 
90
93
  if (!socketMap[webviewId]) {
@@ -112,10 +112,13 @@ const menuConfigWithDefaults = (
112
112
  if (item.type === "divider" || item.type === "separator") {
113
113
  return { type: "divider" };
114
114
  } else {
115
+ // Use shared serialization method
116
+ const actionWithDataId = ffi.internal.serializeMenuAction(item.action || "", item.data);
117
+
115
118
  return {
116
119
  label: item.label || "",
117
120
  type: item.type || "normal",
118
- action: item.action || "",
121
+ action: actionWithDataId,
119
122
  // default enabled to true unless explicitly set to false
120
123
  enabled: item.enabled === false ? false : true,
121
124
  checked: Boolean(item.checked),
@@ -206,15 +206,36 @@ const Updater = {
206
206
  `from-${currentHash}.tar`
207
207
  );
208
208
 
209
+ const bunBinDir = dirname(process.execPath);
210
+ const bspatchPath = join(bunBinDir, "bspatch");
211
+
209
212
  // Note: cwd should be Contents/MacOS/ where the binaries are in the amc app bundle
210
213
  try {
211
- Bun.spawnSync([
212
- "bspatch",
214
+ const patchResult = Bun.spawnSync([
215
+ bspatchPath,
213
216
  currentTarPath,
214
217
  tmpPatchedTarFilePath,
215
218
  patchFilePath,
216
219
  ]);
220
+
221
+ if (patchResult.exitCode !== 0 || patchResult.success === false) {
222
+ const stderr = new TextDecoder().decode(patchResult.stderr || new Uint8Array());
223
+ const stdout = new TextDecoder().decode(patchResult.stdout || new Uint8Array());
224
+ if (updateInfo) {
225
+ updateInfo.error = stderr || `bspatch failed with exit code ${patchResult.exitCode}`;
226
+ }
227
+ console.error(
228
+ "bspatch failed",
229
+ {
230
+ exitCode: patchResult.exitCode,
231
+ stdout,
232
+ stderr,
233
+ },
234
+ );
235
+ break;
236
+ }
217
237
  } catch (error) {
238
+ console.error("bspatch threw", error);
218
239
  break;
219
240
  }
220
241
 
@@ -590,7 +611,15 @@ start "" launcher.exe
590
611
  // Cross-platform app launch
591
612
  switch (currentOS) {
592
613
  case 'macos':
593
- await Bun.spawn(["open", runningAppBundlePath]);
614
+ // Use a detached shell so relaunch survives after killApp terminates the current process
615
+ await Bun.spawn(
616
+ [
617
+ "sh",
618
+ "-c",
619
+ `open "${runningAppBundlePath}" &`,
620
+ ],
621
+ { detached: true }
622
+ );
594
623
  break;
595
624
  case 'win':
596
625
  // On Windows, launch the run.bat file which handles versioning
@@ -606,22 +635,16 @@ start "" launcher.exe
606
635
  Bun.spawn(["sh", "-c", `${linuxLauncher} &`], { detached: true});
607
636
  break;
608
637
  }
609
- // Use native killApp to properly clean up all resources on Windows/Linux
610
- // macOS handles process.exit correctly
611
- if (currentOS === 'linux' || currentOS === 'win') {
612
- try {
613
- native.symbols.killApp();
614
- // Still call process.exit as a fallback
615
- process.exit(0);
616
- } catch (e) {
617
- // Fallback if native binding fails
618
- console.error('Failed to call native killApp:', e);
619
- process.exit(0);
620
- }
621
- } else {
622
- // macOS handles cleanup properly with process.exit
638
+ // Use native killApp to properly clean up all resources
639
+ try {
640
+ native.symbols.killApp();
641
+ // Still call process.exit as a fallback
623
642
  process.exit(0);
624
- }
643
+ } catch (e) {
644
+ // Fallback if native binding fails
645
+ console.error('Failed to call native killApp:', e);
646
+ process.exit(0);
647
+ }
625
648
  }
626
649
  }
627
650
  },
@@ -7,7 +7,50 @@ import { BrowserView } from "../core/BrowserView";
7
7
  import { Updater } from "../core/Updater";
8
8
  import { Tray } from "../core/Tray";
9
9
 
10
+ // Menu data reference system to avoid serialization overhead
11
+ const menuDataRegistry = new Map<string, any>();
12
+ let menuDataCounter = 0;
13
+
14
+ function storeMenuData(data: any): string {
15
+ const id = `menuData_${++menuDataCounter}`;
16
+ menuDataRegistry.set(id, data);
17
+ return id;
18
+ }
19
+
20
+ function getMenuData(id: string): any {
21
+ return menuDataRegistry.get(id);
22
+ }
10
23
 
24
+ function clearMenuData(id: string): void {
25
+ menuDataRegistry.delete(id);
26
+ }
27
+
28
+ // Shared methods for EB delimiter serialization/deserialization
29
+ const ELECTROBUN_DELIMITER = '|EB|';
30
+
31
+ function serializeMenuAction(action: string, data: any): string {
32
+ const dataId = storeMenuData(data);
33
+ return `${ELECTROBUN_DELIMITER}${dataId}|${action}`;
34
+ }
35
+
36
+ function deserializeMenuAction(encodedAction: string): { action: string; data: any } {
37
+ let actualAction = encodedAction;
38
+ let data = undefined;
39
+
40
+ if (encodedAction.startsWith(ELECTROBUN_DELIMITER)) {
41
+ const parts = encodedAction.split('|');
42
+ if (parts.length >= 4) { // ['', 'EB', 'dataId', 'actualAction', ...]
43
+ const dataId = parts[2];
44
+ actualAction = parts.slice(3).join('|'); // Rejoin in case action contains |
45
+ data = getMenuData(dataId);
46
+
47
+ // Clean up data from registry after use
48
+ clearMenuData(dataId);
49
+ }
50
+ }
51
+
52
+ return { action: actualAction, data };
53
+ }
11
54
 
12
55
  // todo: set up FFI, this is already in the webworker.
13
56
 
@@ -765,6 +808,14 @@ export const ffi = {
765
808
  // );
766
809
  // },
767
810
 
811
+ },
812
+ // Internal functions for menu data management
813
+ internal: {
814
+ storeMenuData,
815
+ getMenuData,
816
+ clearMenuData,
817
+ serializeMenuAction,
818
+ deserializeMenuAction,
768
819
  }
769
820
  }
770
821
 
@@ -1103,9 +1154,14 @@ const trayItemHandler = new JSCallback((id, action) => {
1103
1154
  // Note: Some invisible character that doesn't appear in .length
1104
1155
  // is causing issues
1105
1156
  const actionString = (new CString(action).toString() || "").trim();
1157
+
1158
+ // Use shared deserialization method
1159
+ const { action: actualAction, data } = deserializeMenuAction(actionString);
1160
+
1106
1161
  const event = electrobunEventEmitter.events.tray.trayClicked({
1107
1162
  id,
1108
- action: actionString,
1163
+ action: actualAction,
1164
+ data, // Always include data property (undefined if no data)
1109
1165
  });
1110
1166
 
1111
1167
  let result;
@@ -1120,13 +1176,19 @@ const trayItemHandler = new JSCallback((id, action) => {
1120
1176
 
1121
1177
 
1122
1178
  const applicationMenuHandler = new JSCallback((id, action) => {
1179
+ const actionString = new CString(action).toString();
1180
+
1181
+ // Use shared deserialization method
1182
+ const { action: actualAction, data } = deserializeMenuAction(actionString);
1183
+
1123
1184
  const event = electrobunEventEmitter.events.app.applicationMenuClicked({
1124
1185
  id,
1125
- action: new CString(action),
1186
+ action: actualAction,
1187
+ data, // Always include data property (undefined if no data)
1126
1188
  });
1127
1189
 
1128
1190
  // global event
1129
- electrobunEventEmitter.emitEvent(event);
1191
+ electrobunEventEmitter.emitEvent(event);
1130
1192
  }, {
1131
1193
  args: [FFIType.u32, FFIType.cstring],
1132
1194
  returns: FFIType.void,
@@ -1134,8 +1196,14 @@ const applicationMenuHandler = new JSCallback((id, action) => {
1134
1196
  })
1135
1197
 
1136
1198
  const contextMenuHandler = new JSCallback((id, action) => {
1199
+ const actionString = new CString(action).toString();
1200
+
1201
+ // Use shared deserialization method
1202
+ const { action: actualAction, data } = deserializeMenuAction(actionString);
1203
+
1137
1204
  const event = electrobunEventEmitter.events.app.contextMenuClicked({
1138
- action: new CString(action),
1205
+ action: actualAction,
1206
+ data, // Always include data property (undefined if no data)
1139
1207
  });
1140
1208
 
1141
1209
  electrobunEventEmitter.emitEvent(event);
@@ -1329,7 +1397,9 @@ export const internalRpcHandlers = {
1329
1397
  webviewEvent: (params) => {
1330
1398
  console.log('-----------------+webviewEvent', params)
1331
1399
  },
1332
- }
1400
+ },
1401
+
1402
+
1333
1403
  };
1334
1404
 
1335
1405
  // todo: consider renaming to TrayMenuItemConfig
@@ -1340,6 +1410,7 @@ export type MenuItemConfig =
1340
1410
  label: string;
1341
1411
  tooltip?: string;
1342
1412
  action?: string;
1413
+ data?: any;
1343
1414
  submenu?: Array<MenuItemConfig>;
1344
1415
  enabled?: boolean;
1345
1416
  checked?: boolean;
@@ -1353,6 +1424,7 @@ export type ApplicationMenuItemConfig =
1353
1424
  label: string;
1354
1425
  tooltip?: string;
1355
1426
  action?: string;
1427
+ data?: any;
1356
1428
  submenu?: Array<ApplicationMenuItemConfig>;
1357
1429
  enabled?: boolean;
1358
1430
  checked?: boolean;
@@ -1364,6 +1436,7 @@ export type ApplicationMenuItemConfig =
1364
1436
  label?: string;
1365
1437
  tooltip?: string;
1366
1438
  role?: string;
1439
+ data?: any;
1367
1440
  submenu?: Array<ApplicationMenuItemConfig>;
1368
1441
  enabled?: boolean;
1369
1442
  checked?: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "electrobun",
3
- "version": "0.1.10",
3
+ "version": "0.1.13-beta.1",
4
4
  "description": "Build ultra fast, tiny, and cross-platform desktop apps with Typescript.",
5
5
  "license": "MIT",
6
6
  "author": "Blackboard Technologies Inc.",
@@ -48,7 +48,8 @@
48
48
  "push:minor": "npm version minor && git push origin main --tags",
49
49
  "push:major": "npm version major && git push origin main --tags",
50
50
  "build:push:artifacts": "bun scripts/build-and-upload-artifacts.js",
51
- "test": "bun build:dev && bun build:cli && cd tests && npm install && bun build:dev && bun start"
51
+ "test": "bun build:dev && bun build:cli && cd tests && npm install && bun build:dev && bun start",
52
+ "test:bsdiff": "cd src/bsdiff && ../../vendors/zig/zig build test && echo \"bsdiff tests passed\""
52
53
  },
53
54
  "devDependencies": {
54
55
  "@types/archiver": "^6.0.3",
package/src/cli/index.ts CHANGED
@@ -94,13 +94,18 @@ async function ensureCoreDependencies(targetOS?: 'macos' | 'win' | 'linux', targ
94
94
 
95
95
  // Check platform-specific binaries
96
96
  const requiredBinaries = [
97
- platformPaths.BUN_BINARY,
98
- platformPaths.LAUNCHER_RELEASE,
99
- // Platform-specific native wrapper
100
- platformOS === 'macos' ? platformPaths.NATIVE_WRAPPER_MACOS :
101
- platformOS === 'win' ? platformPaths.NATIVE_WRAPPER_WIN :
102
- platformPaths.NATIVE_WRAPPER_LINUX
97
+ platformPaths.BUN_BINARY
103
98
  ];
99
+ if (platformOS === 'macos') {
100
+ requiredBinaries.push(
101
+ platformPaths.LAUNCHER_RELEASE,
102
+ platformPaths.NATIVE_WRAPPER_MACOS
103
+ );
104
+ } else if (platformOS === 'win') {
105
+ requiredBinaries.push(platformPaths.NATIVE_WRAPPER_WIN);
106
+ } else {
107
+ requiredBinaries.push(platformPaths.NATIVE_WRAPPER_LINUX);
108
+ }
104
109
 
105
110
  // Check shared files (main.js should be in shared dist/)
106
111
  const requiredSharedFiles = [
@@ -236,13 +241,17 @@ async function ensureCoreDependencies(targetOS?: 'macos' | 'win' | 'linux', targ
236
241
  }
237
242
 
238
243
  // Verify extraction completed successfully - check platform-specific binaries only
239
- const requiredBinaries = [
240
- platformPaths.BUN_BINARY,
241
- platformPaths.LAUNCHER_RELEASE,
242
- platformOS === 'macos' ? platformPaths.NATIVE_WRAPPER_MACOS :
243
- platformOS === 'win' ? platformPaths.NATIVE_WRAPPER_WIN :
244
- platformPaths.NATIVE_WRAPPER_LINUX
245
- ];
244
+ const requiredBinaries = [platformPaths.BUN_BINARY];
245
+ if (platformOS === 'macos') {
246
+ requiredBinaries.push(
247
+ platformPaths.LAUNCHER_RELEASE,
248
+ platformPaths.NATIVE_WRAPPER_MACOS
249
+ );
250
+ } else if (platformOS === 'win') {
251
+ requiredBinaries.push(platformPaths.NATIVE_WRAPPER_WIN);
252
+ } else {
253
+ requiredBinaries.push(platformPaths.NATIVE_WRAPPER_LINUX);
254
+ }
246
255
 
247
256
  const missingBinaries = requiredBinaries.filter(file => !existsSync(file));
248
257
  if (missingBinaries.length > 0) {
@@ -7,6 +7,6 @@
7
7
  "start": "electrobun build && electrobun dev"
8
8
  },
9
9
  "dependencies": {
10
- "electrobun": "0.0.19-beta.133"
10
+ "electrobun": "latest"
11
11
  }
12
12
  }