Some checks failed
farmcontrol/farmcontrol-ui/pipeline/head There was a failure building this commit
4806 lines
156 KiB
JavaScript
4806 lines
156 KiB
JavaScript
// @bun
|
|
var __defProp = Object.defineProperty;
|
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
var __moduleCache = /* @__PURE__ */ new WeakMap;
|
|
var __toCommonJS = (from) => {
|
|
var entry = __moduleCache.get(from), desc;
|
|
if (entry)
|
|
return entry;
|
|
entry = __defProp({}, "__esModule", { value: true });
|
|
if (from && typeof from === "object" || typeof from === "function")
|
|
__getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
|
|
get: () => from[key],
|
|
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
}));
|
|
__moduleCache.set(from, entry);
|
|
return entry;
|
|
};
|
|
var __export = (target, all) => {
|
|
for (var name in all)
|
|
__defProp(target, name, {
|
|
get: all[name],
|
|
enumerable: true,
|
|
configurable: true,
|
|
set: (newValue) => all[name] = () => newValue
|
|
});
|
|
};
|
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
var __promiseAll = (args) => Promise.all(args);
|
|
|
|
// node_modules/electrobun/dist/api/bun/events/event.ts
|
|
class ElectrobunEvent {
|
|
name;
|
|
data;
|
|
_response;
|
|
responseWasSet = false;
|
|
constructor(name, data) {
|
|
this.name = name;
|
|
this.data = data;
|
|
}
|
|
get response() {
|
|
return this._response;
|
|
}
|
|
set response(value) {
|
|
this._response = value;
|
|
this.responseWasSet = true;
|
|
}
|
|
clearResponse() {
|
|
this._response = undefined;
|
|
this.responseWasSet = false;
|
|
}
|
|
}
|
|
|
|
// node_modules/electrobun/dist/api/bun/events/windowEvents.ts
|
|
var windowEvents_default;
|
|
var init_windowEvents = __esm(() => {
|
|
windowEvents_default = {
|
|
close: (data) => new ElectrobunEvent("close", data),
|
|
resize: (data) => new ElectrobunEvent("resize", data),
|
|
move: (data) => new ElectrobunEvent("move", data),
|
|
focus: (data) => new ElectrobunEvent("focus", data)
|
|
};
|
|
});
|
|
|
|
// node_modules/electrobun/dist/api/bun/events/webviewEvents.ts
|
|
var webviewEvents_default;
|
|
var init_webviewEvents = __esm(() => {
|
|
webviewEvents_default = {
|
|
willNavigate: (data) => new ElectrobunEvent("will-navigate", data),
|
|
didNavigate: (data) => new ElectrobunEvent("did-navigate", data),
|
|
didNavigateInPage: (data) => new ElectrobunEvent("did-navigate-in-page", data),
|
|
didCommitNavigation: (data) => new ElectrobunEvent("did-commit-navigation", data),
|
|
domReady: (data) => new ElectrobunEvent("dom-ready", data),
|
|
newWindowOpen: (data) => new ElectrobunEvent("new-window-open", data),
|
|
hostMessage: (data) => new ElectrobunEvent("host-message", data),
|
|
downloadStarted: (data) => new ElectrobunEvent("download-started", data),
|
|
downloadProgress: (data) => new ElectrobunEvent("download-progress", data),
|
|
downloadCompleted: (data) => new ElectrobunEvent("download-completed", data),
|
|
downloadFailed: (data) => new ElectrobunEvent("download-failed", data)
|
|
};
|
|
});
|
|
|
|
// node_modules/electrobun/dist/api/bun/events/trayEvents.ts
|
|
var trayEvents_default;
|
|
var init_trayEvents = __esm(() => {
|
|
trayEvents_default = {
|
|
trayClicked: (data) => new ElectrobunEvent("tray-clicked", data)
|
|
};
|
|
});
|
|
|
|
// node_modules/electrobun/dist/api/bun/events/ApplicationEvents.ts
|
|
var ApplicationEvents_default;
|
|
var init_ApplicationEvents = __esm(() => {
|
|
ApplicationEvents_default = {
|
|
applicationMenuClicked: (data) => new ElectrobunEvent("application-menu-clicked", data),
|
|
contextMenuClicked: (data) => new ElectrobunEvent("context-menu-clicked", data),
|
|
openUrl: (data) => new ElectrobunEvent("open-url", data),
|
|
beforeQuit: (data) => new ElectrobunEvent("before-quit", data)
|
|
};
|
|
});
|
|
|
|
// node_modules/electrobun/dist/api/bun/events/eventEmitter.ts
|
|
import EventEmitter from "events";
|
|
var ElectrobunEventEmitter, electrobunEventEmitter, eventEmitter_default;
|
|
var init_eventEmitter = __esm(() => {
|
|
init_windowEvents();
|
|
init_webviewEvents();
|
|
init_trayEvents();
|
|
init_ApplicationEvents();
|
|
ElectrobunEventEmitter = class ElectrobunEventEmitter extends EventEmitter {
|
|
constructor() {
|
|
super();
|
|
}
|
|
emitEvent(ElectrobunEvent2, specifier) {
|
|
if (specifier) {
|
|
this.emit(`${ElectrobunEvent2.name}-${specifier}`, ElectrobunEvent2);
|
|
} else {
|
|
this.emit(ElectrobunEvent2.name, ElectrobunEvent2);
|
|
}
|
|
}
|
|
events = {
|
|
window: {
|
|
...windowEvents_default
|
|
},
|
|
webview: {
|
|
...webviewEvents_default
|
|
},
|
|
tray: {
|
|
...trayEvents_default
|
|
},
|
|
app: {
|
|
...ApplicationEvents_default
|
|
}
|
|
};
|
|
};
|
|
electrobunEventEmitter = new ElectrobunEventEmitter;
|
|
eventEmitter_default = electrobunEventEmitter;
|
|
});
|
|
|
|
// node_modules/electrobun/dist/api/shared/rpc.ts
|
|
function missingTransportMethodError(methods, action) {
|
|
const methodsString = methods.map((m) => `"${m}"`).join(", ");
|
|
return new Error(`This RPC instance cannot ${action} because the transport did not provide one or more of these methods: ${methodsString}`);
|
|
}
|
|
function createRPC(options = {}) {
|
|
let debugHooks = {};
|
|
let transport = {};
|
|
let requestHandler = undefined;
|
|
function setTransport(newTransport) {
|
|
if (transport.unregisterHandler)
|
|
transport.unregisterHandler();
|
|
transport = newTransport;
|
|
transport.registerHandler?.(handler);
|
|
}
|
|
function setRequestHandler(h) {
|
|
if (typeof h === "function") {
|
|
requestHandler = h;
|
|
return;
|
|
}
|
|
requestHandler = (method, params) => {
|
|
const handlerFn = h[method];
|
|
if (handlerFn)
|
|
return handlerFn(params);
|
|
const fallbackHandler = h._;
|
|
if (!fallbackHandler)
|
|
throw new Error(`The requested method has no handler: ${String(method)}`);
|
|
return fallbackHandler(method, params);
|
|
};
|
|
}
|
|
const { maxRequestTime = DEFAULT_MAX_REQUEST_TIME } = options;
|
|
if (options.transport)
|
|
setTransport(options.transport);
|
|
if (options.requestHandler)
|
|
setRequestHandler(options.requestHandler);
|
|
if (options._debugHooks)
|
|
debugHooks = options._debugHooks;
|
|
let lastRequestId = 0;
|
|
function getRequestId() {
|
|
if (lastRequestId <= MAX_ID)
|
|
return ++lastRequestId;
|
|
return lastRequestId = 0;
|
|
}
|
|
const requestListeners = new Map;
|
|
const requestTimeouts = new Map;
|
|
function requestFn(method, ...args) {
|
|
const params = args[0];
|
|
return new Promise((resolve, reject) => {
|
|
if (!transport.send)
|
|
throw missingTransportMethodError(["send"], "make requests");
|
|
const requestId = getRequestId();
|
|
const request2 = {
|
|
type: "request",
|
|
id: requestId,
|
|
method,
|
|
params
|
|
};
|
|
requestListeners.set(requestId, { resolve, reject });
|
|
if (maxRequestTime !== Infinity)
|
|
requestTimeouts.set(requestId, setTimeout(() => {
|
|
requestTimeouts.delete(requestId);
|
|
reject(new Error("RPC request timed out."));
|
|
}, maxRequestTime));
|
|
debugHooks.onSend?.(request2);
|
|
transport.send(request2);
|
|
});
|
|
}
|
|
const request = new Proxy(requestFn, {
|
|
get: (target, prop, receiver) => {
|
|
if (prop in target)
|
|
return Reflect.get(target, prop, receiver);
|
|
return (params) => requestFn(prop, params);
|
|
}
|
|
});
|
|
const requestProxy = request;
|
|
function sendFn(message, ...args) {
|
|
const payload = args[0];
|
|
if (!transport.send)
|
|
throw missingTransportMethodError(["send"], "send messages");
|
|
const rpcMessage = {
|
|
type: "message",
|
|
id: message,
|
|
payload
|
|
};
|
|
debugHooks.onSend?.(rpcMessage);
|
|
transport.send(rpcMessage);
|
|
}
|
|
const send = new Proxy(sendFn, {
|
|
get: (target, prop, receiver) => {
|
|
if (prop in target)
|
|
return Reflect.get(target, prop, receiver);
|
|
return (payload) => sendFn(prop, payload);
|
|
}
|
|
});
|
|
const sendProxy = send;
|
|
const messageListeners = new Map;
|
|
const wildcardMessageListeners = new Set;
|
|
function addMessageListener(message, listener) {
|
|
if (!transport.registerHandler)
|
|
throw missingTransportMethodError(["registerHandler"], "register message listeners");
|
|
if (message === "*") {
|
|
wildcardMessageListeners.add(listener);
|
|
return;
|
|
}
|
|
if (!messageListeners.has(message))
|
|
messageListeners.set(message, new Set);
|
|
messageListeners.get(message).add(listener);
|
|
}
|
|
function removeMessageListener(message, listener) {
|
|
if (message === "*") {
|
|
wildcardMessageListeners.delete(listener);
|
|
return;
|
|
}
|
|
messageListeners.get(message)?.delete(listener);
|
|
if (messageListeners.get(message)?.size === 0)
|
|
messageListeners.delete(message);
|
|
}
|
|
async function handler(message) {
|
|
debugHooks.onReceive?.(message);
|
|
if (!("type" in message))
|
|
throw new Error("Message does not contain a type.");
|
|
if (message.type === "request") {
|
|
if (!transport.send || !requestHandler)
|
|
throw missingTransportMethodError(["send", "requestHandler"], "handle requests");
|
|
const { id, method, params } = message;
|
|
let response;
|
|
try {
|
|
response = {
|
|
type: "response",
|
|
id,
|
|
success: true,
|
|
payload: await requestHandler(method, params)
|
|
};
|
|
} catch (error) {
|
|
if (!(error instanceof Error))
|
|
throw error;
|
|
response = {
|
|
type: "response",
|
|
id,
|
|
success: false,
|
|
error: error.message
|
|
};
|
|
}
|
|
debugHooks.onSend?.(response);
|
|
transport.send(response);
|
|
return;
|
|
}
|
|
if (message.type === "response") {
|
|
const timeout = requestTimeouts.get(message.id);
|
|
if (timeout != null)
|
|
clearTimeout(timeout);
|
|
const { resolve, reject } = requestListeners.get(message.id) ?? {};
|
|
if (!message.success)
|
|
reject?.(new Error(message.error));
|
|
else
|
|
resolve?.(message.payload);
|
|
return;
|
|
}
|
|
if (message.type === "message") {
|
|
for (const listener of wildcardMessageListeners)
|
|
listener(message.id, message.payload);
|
|
const listeners = messageListeners.get(message.id);
|
|
if (!listeners)
|
|
return;
|
|
for (const listener of listeners)
|
|
listener(message.payload);
|
|
return;
|
|
}
|
|
throw new Error(`Unexpected RPC message type: ${message.type}`);
|
|
}
|
|
const proxy = { send: sendProxy, request: requestProxy };
|
|
return {
|
|
setTransport,
|
|
setRequestHandler,
|
|
request,
|
|
requestProxy,
|
|
send,
|
|
sendProxy,
|
|
addMessageListener,
|
|
removeMessageListener,
|
|
proxy
|
|
};
|
|
}
|
|
function defineElectrobunRPC(_side, config) {
|
|
const rpcOptions = {
|
|
maxRequestTime: config.maxRequestTime,
|
|
requestHandler: {
|
|
...config.handlers.requests,
|
|
...config.extraRequestHandlers
|
|
},
|
|
transport: {
|
|
registerHandler: () => {}
|
|
}
|
|
};
|
|
const rpc = createRPC(rpcOptions);
|
|
const messageHandlers = config.handlers.messages;
|
|
if (messageHandlers) {
|
|
rpc.addMessageListener("*", (messageName, payload) => {
|
|
const globalHandler = messageHandlers["*"];
|
|
if (globalHandler) {
|
|
globalHandler(messageName, payload);
|
|
}
|
|
const messageHandler = messageHandlers[messageName];
|
|
if (messageHandler) {
|
|
messageHandler(payload);
|
|
}
|
|
});
|
|
}
|
|
return rpc;
|
|
}
|
|
var MAX_ID = 10000000000, DEFAULT_MAX_REQUEST_TIME = 1000;
|
|
|
|
// node_modules/electrobun/dist/api/shared/platform.ts
|
|
import { platform, arch } from "os";
|
|
var platformName, archName, OS, ARCH;
|
|
var init_platform = __esm(() => {
|
|
platformName = platform();
|
|
archName = arch();
|
|
OS = (() => {
|
|
switch (platformName) {
|
|
case "win32":
|
|
return "win";
|
|
case "darwin":
|
|
return "macos";
|
|
case "linux":
|
|
return "linux";
|
|
default:
|
|
throw new Error(`Unsupported platform: ${platformName}`);
|
|
}
|
|
})();
|
|
ARCH = (() => {
|
|
if (OS === "win") {
|
|
return "x64";
|
|
}
|
|
switch (archName) {
|
|
case "arm64":
|
|
return "arm64";
|
|
case "x64":
|
|
return "x64";
|
|
default:
|
|
throw new Error(`Unsupported architecture: ${archName}`);
|
|
}
|
|
})();
|
|
});
|
|
|
|
// node_modules/electrobun/dist/api/shared/naming.ts
|
|
function sanitizeAppName(appName) {
|
|
return appName.replace(/ /g, "");
|
|
}
|
|
function getAppFileName(appName, buildEnvironment) {
|
|
const sanitized = sanitizeAppName(appName);
|
|
return buildEnvironment === "stable" ? sanitized : `${sanitized}-${buildEnvironment}`;
|
|
}
|
|
function getPlatformPrefix(buildEnvironment, os, arch2) {
|
|
return `${buildEnvironment}-${os}-${arch2}`;
|
|
}
|
|
function getTarballFileName(appFileName, os) {
|
|
return os === "macos" ? `${appFileName}.app.tar.zst` : `${appFileName}.tar.zst`;
|
|
}
|
|
|
|
// node_modules/electrobun/dist/api/bun/core/Utils.ts
|
|
var exports_Utils = {};
|
|
__export(exports_Utils, {
|
|
showNotification: () => showNotification,
|
|
showMessageBox: () => showMessageBox,
|
|
showItemInFolder: () => showItemInFolder,
|
|
quit: () => quit,
|
|
paths: () => paths,
|
|
openPath: () => openPath,
|
|
openFileDialog: () => openFileDialog,
|
|
openExternal: () => openExternal,
|
|
moveToTrash: () => moveToTrash,
|
|
clipboardWriteText: () => clipboardWriteText,
|
|
clipboardWriteImage: () => clipboardWriteImage,
|
|
clipboardReadText: () => clipboardReadText,
|
|
clipboardReadImage: () => clipboardReadImage,
|
|
clipboardClear: () => clipboardClear,
|
|
clipboardAvailableFormats: () => clipboardAvailableFormats
|
|
});
|
|
import { homedir, tmpdir } from "os";
|
|
import { join } from "path";
|
|
import { readFileSync } from "fs";
|
|
function getLinuxXdgUserDirs() {
|
|
try {
|
|
const content = readFileSync(join(home, ".config", "user-dirs.dirs"), "utf-8");
|
|
const dirs = {};
|
|
for (const line of content.split(`
|
|
`)) {
|
|
const trimmed = line.trim();
|
|
if (trimmed.startsWith("#") || !trimmed.includes("="))
|
|
continue;
|
|
const eqIdx = trimmed.indexOf("=");
|
|
const key = trimmed.slice(0, eqIdx);
|
|
let value = trimmed.slice(eqIdx + 1);
|
|
if (value.startsWith('"') && value.endsWith('"')) {
|
|
value = value.slice(1, -1);
|
|
}
|
|
value = value.replace(/\$HOME/g, home);
|
|
dirs[key] = value;
|
|
}
|
|
return dirs;
|
|
} catch {
|
|
return {};
|
|
}
|
|
}
|
|
function xdgUserDir(key, fallbackName) {
|
|
if (OS !== "linux")
|
|
return "";
|
|
if (!_xdgUserDirs)
|
|
_xdgUserDirs = getLinuxXdgUserDirs();
|
|
return _xdgUserDirs[key] || join(home, fallbackName);
|
|
}
|
|
function getVersionInfo() {
|
|
if (_versionInfo)
|
|
return _versionInfo;
|
|
try {
|
|
const resourcesDir = "Resources";
|
|
const raw = readFileSync(join("..", resourcesDir, "version.json"), "utf-8");
|
|
const parsed = JSON.parse(raw);
|
|
_versionInfo = { identifier: parsed.identifier, channel: parsed.channel };
|
|
return _versionInfo;
|
|
} catch (error) {
|
|
console.error("Failed to read version.json", error);
|
|
throw error;
|
|
}
|
|
}
|
|
function getAppDataDir() {
|
|
switch (OS) {
|
|
case "macos":
|
|
return join(home, "Library", "Application Support");
|
|
case "win":
|
|
return process.env["LOCALAPPDATA"] || join(home, "AppData", "Local");
|
|
case "linux":
|
|
return process.env["XDG_DATA_HOME"] || join(home, ".local", "share");
|
|
}
|
|
}
|
|
function getCacheDir() {
|
|
switch (OS) {
|
|
case "macos":
|
|
return join(home, "Library", "Caches");
|
|
case "win":
|
|
return process.env["LOCALAPPDATA"] || join(home, "AppData", "Local");
|
|
case "linux":
|
|
return process.env["XDG_CACHE_HOME"] || join(home, ".cache");
|
|
}
|
|
}
|
|
function getLogsDir() {
|
|
switch (OS) {
|
|
case "macos":
|
|
return join(home, "Library", "Logs");
|
|
case "win":
|
|
return process.env["LOCALAPPDATA"] || join(home, "AppData", "Local");
|
|
case "linux":
|
|
return process.env["XDG_STATE_HOME"] || join(home, ".local", "state");
|
|
}
|
|
}
|
|
function getConfigDir() {
|
|
switch (OS) {
|
|
case "macos":
|
|
return join(home, "Library", "Application Support");
|
|
case "win":
|
|
return process.env["APPDATA"] || join(home, "AppData", "Roaming");
|
|
case "linux":
|
|
return process.env["XDG_CONFIG_HOME"] || join(home, ".config");
|
|
}
|
|
}
|
|
function getUserDir(macName, winName, xdgKey, fallbackName) {
|
|
switch (OS) {
|
|
case "macos":
|
|
return join(home, macName);
|
|
case "win": {
|
|
const userProfile = process.env["USERPROFILE"] || home;
|
|
return join(userProfile, winName);
|
|
}
|
|
case "linux":
|
|
return xdgUserDir(xdgKey, fallbackName);
|
|
}
|
|
}
|
|
var moveToTrash = (path) => {
|
|
return ffi.request.moveToTrash({ path });
|
|
}, showItemInFolder = (path) => {
|
|
return ffi.request.showItemInFolder({ path });
|
|
}, openExternal = (url) => {
|
|
return ffi.request.openExternal({ url });
|
|
}, openPath = (path) => {
|
|
return ffi.request.openPath({ path });
|
|
}, showNotification = (options) => {
|
|
const { title, body, subtitle, silent } = options;
|
|
ffi.request.showNotification({ title, body, subtitle, silent });
|
|
}, isQuitting = false, quit = () => {
|
|
if (isQuitting)
|
|
return;
|
|
isQuitting = true;
|
|
const beforeQuitEvent = electrobunEventEmitter.events.app.beforeQuit({});
|
|
electrobunEventEmitter.emitEvent(beforeQuitEvent);
|
|
if (beforeQuitEvent.responseWasSet && beforeQuitEvent.response?.allow === false) {
|
|
isQuitting = false;
|
|
return;
|
|
}
|
|
native.symbols.stopEventLoop();
|
|
native.symbols.waitForShutdownComplete(5000);
|
|
native.symbols.forceExit(0);
|
|
}, openFileDialog = async (opts = {}) => {
|
|
const optsWithDefault = {
|
|
...{
|
|
startingFolder: "~/",
|
|
allowedFileTypes: "*",
|
|
canChooseFiles: true,
|
|
canChooseDirectory: true,
|
|
allowsMultipleSelection: true
|
|
},
|
|
...opts
|
|
};
|
|
const result = await ffi.request.openFileDialog({
|
|
startingFolder: optsWithDefault.startingFolder,
|
|
allowedFileTypes: optsWithDefault.allowedFileTypes,
|
|
canChooseFiles: optsWithDefault.canChooseFiles,
|
|
canChooseDirectory: optsWithDefault.canChooseDirectory,
|
|
allowsMultipleSelection: optsWithDefault.allowsMultipleSelection
|
|
});
|
|
const filePaths = result.split(",");
|
|
return filePaths;
|
|
}, showMessageBox = async (opts = {}) => {
|
|
const {
|
|
type = "info",
|
|
title = "",
|
|
message = "",
|
|
detail = "",
|
|
buttons = ["OK"],
|
|
defaultId = 0,
|
|
cancelId = -1
|
|
} = opts;
|
|
const response = ffi.request.showMessageBox({
|
|
type,
|
|
title,
|
|
message,
|
|
detail,
|
|
buttons,
|
|
defaultId,
|
|
cancelId
|
|
});
|
|
return { response };
|
|
}, clipboardReadText = () => {
|
|
return ffi.request.clipboardReadText();
|
|
}, clipboardWriteText = (text) => {
|
|
ffi.request.clipboardWriteText({ text });
|
|
}, clipboardReadImage = () => {
|
|
return ffi.request.clipboardReadImage();
|
|
}, clipboardWriteImage = (pngData) => {
|
|
ffi.request.clipboardWriteImage({ pngData });
|
|
}, clipboardClear = () => {
|
|
ffi.request.clipboardClear();
|
|
}, clipboardAvailableFormats = () => {
|
|
return ffi.request.clipboardAvailableFormats();
|
|
}, home, _xdgUserDirs, _versionInfo, paths;
|
|
var init_Utils = __esm(async () => {
|
|
init_eventEmitter();
|
|
init_platform();
|
|
await init_native();
|
|
process.exit = (code) => {
|
|
if (isQuitting) {
|
|
native.symbols.forceExit(code ?? 0);
|
|
return;
|
|
}
|
|
quit();
|
|
};
|
|
home = homedir();
|
|
paths = {
|
|
get home() {
|
|
return home;
|
|
},
|
|
get appData() {
|
|
return getAppDataDir();
|
|
},
|
|
get config() {
|
|
return getConfigDir();
|
|
},
|
|
get cache() {
|
|
return getCacheDir();
|
|
},
|
|
get temp() {
|
|
return tmpdir();
|
|
},
|
|
get logs() {
|
|
return getLogsDir();
|
|
},
|
|
get documents() {
|
|
return getUserDir("Documents", "Documents", "XDG_DOCUMENTS_DIR", "Documents");
|
|
},
|
|
get downloads() {
|
|
return getUserDir("Downloads", "Downloads", "XDG_DOWNLOAD_DIR", "Downloads");
|
|
},
|
|
get desktop() {
|
|
return getUserDir("Desktop", "Desktop", "XDG_DESKTOP_DIR", "Desktop");
|
|
},
|
|
get pictures() {
|
|
return getUserDir("Pictures", "Pictures", "XDG_PICTURES_DIR", "Pictures");
|
|
},
|
|
get music() {
|
|
return getUserDir("Music", "Music", "XDG_MUSIC_DIR", "Music");
|
|
},
|
|
get videos() {
|
|
return getUserDir("Movies", "Videos", "XDG_VIDEOS_DIR", "Videos");
|
|
},
|
|
get userData() {
|
|
const { identifier, channel } = getVersionInfo();
|
|
return join(getAppDataDir(), identifier, channel);
|
|
},
|
|
get userCache() {
|
|
const { identifier, channel } = getVersionInfo();
|
|
return join(getCacheDir(), identifier, channel);
|
|
},
|
|
get userLogs() {
|
|
const { identifier, channel } = getVersionInfo();
|
|
return join(getLogsDir(), identifier, channel);
|
|
}
|
|
};
|
|
});
|
|
|
|
// node_modules/electrobun/dist/api/bun/core/Updater.ts
|
|
import { join as join2, dirname, resolve } from "path";
|
|
import { homedir as homedir2 } from "os";
|
|
import {
|
|
renameSync,
|
|
unlinkSync,
|
|
mkdirSync,
|
|
rmdirSync,
|
|
statSync,
|
|
readdirSync
|
|
} from "fs";
|
|
import { execSync } from "child_process";
|
|
function emitStatus(status, message, details) {
|
|
const entry = {
|
|
status,
|
|
message,
|
|
timestamp: Date.now(),
|
|
details
|
|
};
|
|
statusHistory.push(entry);
|
|
if (onStatusChangeCallback) {
|
|
onStatusChangeCallback(entry);
|
|
}
|
|
}
|
|
function getAppDataDir2() {
|
|
switch (OS) {
|
|
case "macos":
|
|
return join2(homedir2(), "Library", "Application Support");
|
|
case "win":
|
|
return process.env["LOCALAPPDATA"] || join2(homedir2(), "AppData", "Local");
|
|
case "linux":
|
|
return process.env["XDG_DATA_HOME"] || join2(homedir2(), ".local", "share");
|
|
default:
|
|
return join2(homedir2(), ".config");
|
|
}
|
|
}
|
|
function cleanupExtractionFolder(extractionFolder, keepTarHash) {
|
|
const keepFile = `${keepTarHash}.tar`;
|
|
try {
|
|
const entries = readdirSync(extractionFolder);
|
|
for (const entry of entries) {
|
|
if (entry === keepFile)
|
|
continue;
|
|
const fullPath = join2(extractionFolder, entry);
|
|
try {
|
|
const s = statSync(fullPath);
|
|
if (s.isDirectory()) {
|
|
rmdirSync(fullPath, { recursive: true });
|
|
} else {
|
|
unlinkSync(fullPath);
|
|
}
|
|
} catch (e) {}
|
|
}
|
|
} catch (e) {}
|
|
}
|
|
var statusHistory, onStatusChangeCallback = null, localInfo, updateInfo, Updater;
|
|
var init_Updater = __esm(async () => {
|
|
init_platform();
|
|
await init_Utils();
|
|
statusHistory = [];
|
|
Updater = {
|
|
updateInfo: () => {
|
|
return updateInfo;
|
|
},
|
|
getStatusHistory: () => {
|
|
return [...statusHistory];
|
|
},
|
|
clearStatusHistory: () => {
|
|
statusHistory.length = 0;
|
|
},
|
|
onStatusChange: (callback) => {
|
|
onStatusChangeCallback = callback;
|
|
},
|
|
checkForUpdate: async () => {
|
|
emitStatus("checking", "Checking for updates...");
|
|
const localInfo2 = await Updater.getLocallocalInfo();
|
|
if (localInfo2.channel === "dev") {
|
|
emitStatus("no-update", "Dev channel - updates disabled", {
|
|
currentHash: localInfo2.hash
|
|
});
|
|
return {
|
|
version: localInfo2.version,
|
|
hash: localInfo2.hash,
|
|
updateAvailable: false,
|
|
updateReady: false,
|
|
error: ""
|
|
};
|
|
}
|
|
const cacheBuster = Math.random().toString(36).substring(7);
|
|
const platformPrefix = getPlatformPrefix(localInfo2.channel, OS, ARCH);
|
|
const updateInfoUrl = `${localInfo2.baseUrl.replace(/\/+$/, "")}/${platformPrefix}-update.json?${cacheBuster}`;
|
|
try {
|
|
const updateInfoResponse = await fetch(updateInfoUrl);
|
|
if (updateInfoResponse.ok) {
|
|
const responseText = await updateInfoResponse.text();
|
|
try {
|
|
updateInfo = JSON.parse(responseText);
|
|
} catch {
|
|
emitStatus("error", "Invalid update.json: failed to parse JSON", {
|
|
url: updateInfoUrl
|
|
});
|
|
return {
|
|
version: "",
|
|
hash: "",
|
|
updateAvailable: false,
|
|
updateReady: false,
|
|
error: `Invalid update.json: failed to parse JSON`
|
|
};
|
|
}
|
|
if (!updateInfo.hash) {
|
|
emitStatus("error", "Invalid update.json: missing hash", {
|
|
url: updateInfoUrl
|
|
});
|
|
return {
|
|
version: "",
|
|
hash: "",
|
|
updateAvailable: false,
|
|
updateReady: false,
|
|
error: `Invalid update.json: missing hash`
|
|
};
|
|
}
|
|
if (updateInfo.hash !== localInfo2.hash) {
|
|
updateInfo.updateAvailable = true;
|
|
emitStatus("update-available", `Update available: ${localInfo2.hash.slice(0, 8)} \u2192 ${updateInfo.hash.slice(0, 8)}`, {
|
|
currentHash: localInfo2.hash,
|
|
latestHash: updateInfo.hash
|
|
});
|
|
} else {
|
|
emitStatus("no-update", "Already on latest version", {
|
|
currentHash: localInfo2.hash
|
|
});
|
|
}
|
|
} else {
|
|
emitStatus("error", `Failed to fetch update info (HTTP ${updateInfoResponse.status})`, { url: updateInfoUrl });
|
|
return {
|
|
version: "",
|
|
hash: "",
|
|
updateAvailable: false,
|
|
updateReady: false,
|
|
error: `Failed to fetch update info from ${updateInfoUrl}`
|
|
};
|
|
}
|
|
} catch (error) {
|
|
return {
|
|
version: "",
|
|
hash: "",
|
|
updateAvailable: false,
|
|
updateReady: false,
|
|
error: `Failed to fetch update info from ${updateInfoUrl}`
|
|
};
|
|
}
|
|
return updateInfo;
|
|
},
|
|
downloadUpdate: async () => {
|
|
emitStatus("download-starting", "Starting update download...");
|
|
const appDataFolder = await Updater.appDataFolder();
|
|
await Updater.channelBucketUrl();
|
|
const appFileName = localInfo.name;
|
|
let currentHash = (await Updater.getLocallocalInfo()).hash;
|
|
let latestHash = (await Updater.checkForUpdate()).hash;
|
|
const extractionFolder = join2(appDataFolder, "self-extraction");
|
|
if (!await Bun.file(extractionFolder).exists()) {
|
|
mkdirSync(extractionFolder, { recursive: true });
|
|
}
|
|
let currentTarPath = join2(extractionFolder, `${currentHash}.tar`);
|
|
const latestTarPath = join2(extractionFolder, `${latestHash}.tar`);
|
|
const seenHashes = [];
|
|
let patchesApplied = 0;
|
|
let usedPatchPath = false;
|
|
if (!await Bun.file(latestTarPath).exists()) {
|
|
emitStatus("checking-local-tar", `Checking for local tar file: ${currentHash.slice(0, 8)}`, { currentHash });
|
|
while (currentHash !== latestHash) {
|
|
seenHashes.push(currentHash);
|
|
const currentTar = Bun.file(currentTarPath);
|
|
if (!await currentTar.exists()) {
|
|
emitStatus("local-tar-missing", `Local tar not found for ${currentHash.slice(0, 8)}, will download full bundle`, { currentHash });
|
|
break;
|
|
}
|
|
emitStatus("local-tar-found", `Found local tar for ${currentHash.slice(0, 8)}`, { currentHash });
|
|
const platformPrefix = getPlatformPrefix(localInfo.channel, OS, ARCH);
|
|
const patchUrl = `${localInfo.baseUrl.replace(/\/+$/, "")}/${platformPrefix}-${currentHash}.patch`;
|
|
emitStatus("fetching-patch", `Checking for patch: ${currentHash.slice(0, 8)}`, { currentHash, url: patchUrl });
|
|
const patchResponse = await fetch(patchUrl);
|
|
if (!patchResponse.ok) {
|
|
emitStatus("patch-not-found", `No patch available for ${currentHash.slice(0, 8)}, will download full bundle`, { currentHash });
|
|
break;
|
|
}
|
|
emitStatus("patch-found", `Patch found for ${currentHash.slice(0, 8)}`, { currentHash });
|
|
emitStatus("downloading-patch", `Downloading patch for ${currentHash.slice(0, 8)}...`, { currentHash });
|
|
const patchFilePath = join2(appDataFolder, "self-extraction", `${currentHash}.patch`);
|
|
await Bun.write(patchFilePath, await patchResponse.arrayBuffer());
|
|
const tmpPatchedTarFilePath = join2(appDataFolder, "self-extraction", `from-${currentHash}.tar`);
|
|
const bunBinDir = dirname(process.execPath);
|
|
const bspatchBinName = OS === "win" ? "bspatch.exe" : "bspatch";
|
|
const bspatchPath = join2(bunBinDir, bspatchBinName);
|
|
emitStatus("applying-patch", `Applying patch ${patchesApplied + 1} for ${currentHash.slice(0, 8)}...`, {
|
|
currentHash,
|
|
patchNumber: patchesApplied + 1
|
|
});
|
|
if (!statSync(bspatchPath, { throwIfNoEntry: false })) {
|
|
emitStatus("patch-failed", `bspatch binary not found at ${bspatchPath}`, {
|
|
currentHash,
|
|
errorMessage: `bspatch not found: ${bspatchPath}`
|
|
});
|
|
console.error("bspatch not found:", bspatchPath);
|
|
break;
|
|
}
|
|
if (!statSync(currentTarPath, { throwIfNoEntry: false })) {
|
|
emitStatus("patch-failed", `Old tar not found at ${currentTarPath}`, {
|
|
currentHash,
|
|
errorMessage: `old tar not found: ${currentTarPath}`
|
|
});
|
|
console.error("old tar not found:", currentTarPath);
|
|
break;
|
|
}
|
|
if (!statSync(patchFilePath, { throwIfNoEntry: false })) {
|
|
emitStatus("patch-failed", `Patch file not found at ${patchFilePath}`, {
|
|
currentHash,
|
|
errorMessage: `patch not found: ${patchFilePath}`
|
|
});
|
|
console.error("patch file not found:", patchFilePath);
|
|
break;
|
|
}
|
|
try {
|
|
const patchResult = Bun.spawnSync([
|
|
bspatchPath,
|
|
currentTarPath,
|
|
tmpPatchedTarFilePath,
|
|
patchFilePath
|
|
]);
|
|
if (patchResult.exitCode !== 0 || patchResult.success === false) {
|
|
const stderr = patchResult.stderr ? patchResult.stderr.toString() : "";
|
|
const stdout = patchResult.stdout ? patchResult.stdout.toString() : "";
|
|
if (updateInfo) {
|
|
updateInfo.error = stderr || `bspatch failed with exit code ${patchResult.exitCode}`;
|
|
}
|
|
emitStatus("patch-failed", `Patch application failed: ${stderr || `exit code ${patchResult.exitCode}`}`, {
|
|
currentHash,
|
|
errorMessage: stderr || `exit code ${patchResult.exitCode}`
|
|
});
|
|
console.error("bspatch failed", {
|
|
exitCode: patchResult.exitCode,
|
|
stdout,
|
|
stderr,
|
|
bspatchPath,
|
|
oldTar: currentTarPath,
|
|
newTar: tmpPatchedTarFilePath,
|
|
patch: patchFilePath
|
|
});
|
|
break;
|
|
}
|
|
} catch (error) {
|
|
emitStatus("patch-failed", `Patch threw exception: ${error.message}`, {
|
|
currentHash,
|
|
errorMessage: error.message
|
|
});
|
|
console.error("bspatch threw", error, { bspatchPath });
|
|
break;
|
|
}
|
|
patchesApplied++;
|
|
emitStatus("patch-applied", `Patch ${patchesApplied} applied successfully`, {
|
|
currentHash,
|
|
patchNumber: patchesApplied
|
|
});
|
|
emitStatus("extracting-version", "Extracting version info from patched tar...", { currentHash });
|
|
let hashFilePath = "";
|
|
const resourcesDir = "Resources";
|
|
const patchedTarBytes = await Bun.file(tmpPatchedTarFilePath).arrayBuffer();
|
|
const patchedArchive = new Bun.Archive(patchedTarBytes);
|
|
const patchedFiles = await patchedArchive.files();
|
|
for (const [filePath] of patchedFiles) {
|
|
if (filePath.endsWith(`${resourcesDir}/version.json`) || filePath.endsWith("metadata.json")) {
|
|
hashFilePath = filePath;
|
|
break;
|
|
}
|
|
}
|
|
if (!hashFilePath) {
|
|
emitStatus("error", "Could not find version/metadata file in patched tar", { currentHash });
|
|
console.error("Neither Resources/version.json nor metadata.json found in patched tar:", tmpPatchedTarFilePath);
|
|
break;
|
|
}
|
|
const hashFile = patchedFiles.get(hashFilePath);
|
|
const hashFileJson = JSON.parse(await hashFile.text());
|
|
const nextHash = hashFileJson.hash;
|
|
if (seenHashes.includes(nextHash)) {
|
|
emitStatus("error", "Cyclical update detected, falling back to full download", { currentHash: nextHash });
|
|
console.log("Warning: cyclical update detected");
|
|
break;
|
|
}
|
|
seenHashes.push(nextHash);
|
|
if (!nextHash) {
|
|
emitStatus("error", "Could not determine next hash from patched tar", { currentHash });
|
|
break;
|
|
}
|
|
const updatedTarPath = join2(appDataFolder, "self-extraction", `${nextHash}.tar`);
|
|
renameSync(tmpPatchedTarFilePath, updatedTarPath);
|
|
unlinkSync(currentTarPath);
|
|
unlinkSync(patchFilePath);
|
|
currentHash = nextHash;
|
|
currentTarPath = join2(appDataFolder, "self-extraction", `${currentHash}.tar`);
|
|
emitStatus("patch-applied", `Patched to ${nextHash.slice(0, 8)}, checking for more patches...`, {
|
|
currentHash: nextHash,
|
|
toHash: latestHash,
|
|
totalPatchesApplied: patchesApplied
|
|
});
|
|
}
|
|
if (currentHash === latestHash && patchesApplied > 0) {
|
|
usedPatchPath = true;
|
|
emitStatus("patch-chain-complete", `Patch chain complete! Applied ${patchesApplied} patches`, {
|
|
totalPatchesApplied: patchesApplied,
|
|
currentHash: latestHash,
|
|
usedPatchPath: true
|
|
});
|
|
}
|
|
if (currentHash !== latestHash) {
|
|
emitStatus("downloading-full-bundle", "Downloading full update bundle...", {
|
|
currentHash,
|
|
latestHash,
|
|
usedPatchPath: false
|
|
});
|
|
const cacheBuster = Math.random().toString(36).substring(7);
|
|
const platformPrefix = getPlatformPrefix(localInfo.channel, OS, ARCH);
|
|
const tarballName = getTarballFileName(appFileName, OS);
|
|
const urlToLatestTarball = `${localInfo.baseUrl.replace(/\/+$/, "")}/${platformPrefix}-${tarballName}`;
|
|
const prevVersionCompressedTarballPath = join2(appDataFolder, "self-extraction", "latest.tar.zst");
|
|
emitStatus("download-progress", `Fetching ${tarballName}...`, {
|
|
url: urlToLatestTarball
|
|
});
|
|
const response = await fetch(urlToLatestTarball + `?${cacheBuster}`);
|
|
if (response.ok && response.body) {
|
|
const contentLength = response.headers.get("content-length");
|
|
const totalBytes = contentLength ? parseInt(contentLength, 10) : undefined;
|
|
let bytesDownloaded = 0;
|
|
const reader = response.body.getReader();
|
|
const writer = Bun.file(prevVersionCompressedTarballPath).writer();
|
|
while (true) {
|
|
const { done, value } = await reader.read();
|
|
if (done)
|
|
break;
|
|
await writer.write(value);
|
|
bytesDownloaded += value.length;
|
|
if (bytesDownloaded % 500000 < value.length) {
|
|
emitStatus("download-progress", `Downloading: ${(bytesDownloaded / 1024 / 1024).toFixed(1)} MB`, {
|
|
bytesDownloaded,
|
|
totalBytes,
|
|
progress: totalBytes ? Math.round(bytesDownloaded / totalBytes * 100) : undefined
|
|
});
|
|
}
|
|
}
|
|
await writer.flush();
|
|
writer.end();
|
|
emitStatus("download-progress", `Download complete: ${(bytesDownloaded / 1024 / 1024).toFixed(1)} MB`, {
|
|
bytesDownloaded,
|
|
totalBytes,
|
|
progress: 100
|
|
});
|
|
} else {
|
|
emitStatus("error", `Failed to download: ${urlToLatestTarball}`, {
|
|
url: urlToLatestTarball
|
|
});
|
|
console.log("latest version not found at: ", urlToLatestTarball);
|
|
}
|
|
emitStatus("decompressing", "Decompressing update bundle...");
|
|
const bunBinDir = dirname(process.execPath);
|
|
const zstdBinName = OS === "win" ? "zig-zstd.exe" : "zig-zstd";
|
|
const zstdPath = join2(bunBinDir, zstdBinName);
|
|
if (!statSync(zstdPath, { throwIfNoEntry: false })) {
|
|
updateInfo.error = `zig-zstd not found: ${zstdPath}`;
|
|
emitStatus("error", updateInfo.error, { zstdPath });
|
|
console.error("zig-zstd not found:", zstdPath);
|
|
} else {
|
|
const decompressResult = Bun.spawnSync([
|
|
zstdPath,
|
|
"decompress",
|
|
"-i",
|
|
prevVersionCompressedTarballPath,
|
|
"-o",
|
|
latestTarPath,
|
|
"--no-timing"
|
|
], {
|
|
cwd: extractionFolder,
|
|
stdout: "inherit",
|
|
stderr: "inherit"
|
|
});
|
|
if (!decompressResult.success) {
|
|
updateInfo.error = `zig-zstd failed with exit code ${decompressResult.exitCode}`;
|
|
emitStatus("error", updateInfo.error, {
|
|
zstdPath,
|
|
exitCode: decompressResult.exitCode
|
|
});
|
|
console.error("zig-zstd failed", {
|
|
exitCode: decompressResult.exitCode,
|
|
zstdPath
|
|
});
|
|
} else {
|
|
emitStatus("decompressing", "Decompression complete");
|
|
}
|
|
}
|
|
unlinkSync(prevVersionCompressedTarballPath);
|
|
}
|
|
}
|
|
if (await Bun.file(latestTarPath).exists()) {
|
|
updateInfo.updateReady = true;
|
|
emitStatus("download-complete", `Update ready to install (used ${usedPatchPath ? "patch" : "full download"} path)`, {
|
|
latestHash,
|
|
usedPatchPath,
|
|
totalPatchesApplied: patchesApplied
|
|
});
|
|
} else {
|
|
updateInfo.error = "Failed to download latest version";
|
|
emitStatus("error", "Failed to download latest version", { latestHash });
|
|
}
|
|
cleanupExtractionFolder(extractionFolder, latestHash);
|
|
},
|
|
applyUpdate: async () => {
|
|
if (updateInfo?.updateReady) {
|
|
emitStatus("applying", "Starting update installation...");
|
|
const appDataFolder = await Updater.appDataFolder();
|
|
const extractionFolder = join2(appDataFolder, "self-extraction");
|
|
if (!await Bun.file(extractionFolder).exists()) {
|
|
mkdirSync(extractionFolder, { recursive: true });
|
|
}
|
|
let latestHash = (await Updater.checkForUpdate()).hash;
|
|
const latestTarPath = join2(extractionFolder, `${latestHash}.tar`);
|
|
let appBundleSubpath = "";
|
|
if (await Bun.file(latestTarPath).exists()) {
|
|
emitStatus("extracting", `Extracting update to ${latestHash.slice(0, 8)}...`, { latestHash });
|
|
const extractionDir = OS === "win" ? join2(extractionFolder, `temp-${latestHash}`) : extractionFolder;
|
|
if (OS === "win") {
|
|
mkdirSync(extractionDir, { recursive: true });
|
|
}
|
|
const latestTarBytes = await Bun.file(latestTarPath).arrayBuffer();
|
|
const latestArchive = new Bun.Archive(latestTarBytes);
|
|
await latestArchive.extract(extractionDir);
|
|
if (OS === "macos") {
|
|
const extractedFiles = readdirSync(extractionDir);
|
|
for (const file of extractedFiles) {
|
|
if (file.endsWith(".app")) {
|
|
appBundleSubpath = file + "/";
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
appBundleSubpath = "./";
|
|
}
|
|
console.log(`Tar extraction completed. Found appBundleSubpath: ${appBundleSubpath}`);
|
|
if (!appBundleSubpath) {
|
|
console.error("Failed to find app in tarball");
|
|
return;
|
|
}
|
|
const extractedAppPath = resolve(join2(extractionDir, appBundleSubpath));
|
|
let newAppBundlePath;
|
|
if (OS === "linux") {
|
|
const extractedFiles = readdirSync(extractionDir);
|
|
const appBundleDir = extractedFiles.find((file) => {
|
|
const filePath = join2(extractionDir, file);
|
|
return statSync(filePath).isDirectory() && !file.endsWith(".tar");
|
|
});
|
|
if (!appBundleDir) {
|
|
console.error("Could not find app bundle directory in extraction");
|
|
return;
|
|
}
|
|
newAppBundlePath = join2(extractionDir, appBundleDir);
|
|
const bundleStats = statSync(newAppBundlePath, { throwIfNoEntry: false });
|
|
if (!bundleStats || !bundleStats.isDirectory()) {
|
|
console.error(`App bundle directory not found at: ${newAppBundlePath}`);
|
|
console.log("Contents of extraction directory:");
|
|
try {
|
|
const files = readdirSync(extractionDir);
|
|
for (const file of files) {
|
|
console.log(` - ${file}`);
|
|
const subPath = join2(extractionDir, file);
|
|
if (statSync(subPath).isDirectory()) {
|
|
const subFiles = readdirSync(subPath);
|
|
for (const subFile of subFiles) {
|
|
console.log(` - ${subFile}`);
|
|
}
|
|
}
|
|
}
|
|
} catch (e) {
|
|
console.log("Could not list directory contents:", e);
|
|
}
|
|
return;
|
|
}
|
|
} else if (OS === "win") {
|
|
const appBundleName = getAppFileName(localInfo.name, localInfo.channel);
|
|
newAppBundlePath = join2(extractionDir, appBundleName);
|
|
if (!statSync(newAppBundlePath, { throwIfNoEntry: false })) {
|
|
console.error(`Extracted app not found at: ${newAppBundlePath}`);
|
|
console.log("Contents of extraction directory:");
|
|
try {
|
|
const files = readdirSync(extractionDir);
|
|
for (const file of files) {
|
|
console.log(` - ${file}`);
|
|
}
|
|
} catch (e) {
|
|
console.log("Could not list directory contents:", e);
|
|
}
|
|
return;
|
|
}
|
|
} else {
|
|
newAppBundlePath = extractedAppPath;
|
|
}
|
|
let runningAppBundlePath;
|
|
const appDataFolder2 = await Updater.appDataFolder();
|
|
if (OS === "macos") {
|
|
runningAppBundlePath = resolve(dirname(process.execPath), "..", "..");
|
|
} else if (OS === "linux" || OS === "win") {
|
|
runningAppBundlePath = join2(appDataFolder2, "app");
|
|
} else {
|
|
throw new Error(`Unsupported platform: ${OS}`);
|
|
}
|
|
try {
|
|
emitStatus("replacing-app", "Removing old version...");
|
|
if (OS === "macos") {
|
|
if (statSync(runningAppBundlePath, { throwIfNoEntry: false })) {
|
|
rmdirSync(runningAppBundlePath, { recursive: true });
|
|
}
|
|
emitStatus("replacing-app", "Installing new version...");
|
|
renameSync(newAppBundlePath, runningAppBundlePath);
|
|
try {
|
|
execSync(`xattr -r -d com.apple.quarantine "${runningAppBundlePath}"`, { stdio: "ignore" });
|
|
} catch (e) {}
|
|
} else if (OS === "linux") {
|
|
const appBundleDir = join2(appDataFolder2, "app");
|
|
if (statSync(appBundleDir, { throwIfNoEntry: false })) {
|
|
rmdirSync(appBundleDir, { recursive: true });
|
|
}
|
|
renameSync(newAppBundlePath, appBundleDir);
|
|
const launcherPath = join2(appBundleDir, "bin", "launcher");
|
|
if (statSync(launcherPath, { throwIfNoEntry: false })) {
|
|
execSync(`chmod +x "${launcherPath}"`);
|
|
}
|
|
const bunPath = join2(appBundleDir, "bin", "bun");
|
|
if (statSync(bunPath, { throwIfNoEntry: false })) {
|
|
execSync(`chmod +x "${bunPath}"`);
|
|
}
|
|
}
|
|
if (OS !== "win") {
|
|
cleanupExtractionFolder(extractionFolder, latestHash);
|
|
}
|
|
if (OS === "win") {
|
|
const parentDir = dirname(runningAppBundlePath);
|
|
const updateScriptPath = join2(parentDir, "update.bat");
|
|
const launcherPath = join2(runningAppBundlePath, "bin", "launcher.exe");
|
|
const runningAppWin = runningAppBundlePath.replace(/\//g, "\\");
|
|
const newAppWin = newAppBundlePath.replace(/\//g, "\\");
|
|
const extractionDirWin = extractionDir.replace(/\//g, "\\");
|
|
const launcherPathWin = launcherPath.replace(/\//g, "\\");
|
|
const updateScript = `@echo off
|
|
setlocal
|
|
|
|
:: Wait for the app to fully exit (check if launcher.exe is still running)
|
|
:waitloop
|
|
tasklist /FI "IMAGENAME eq launcher.exe" 2>NUL | find /I /N "launcher.exe">NUL
|
|
if "%ERRORLEVEL%"=="0" (
|
|
timeout /t 1 /nobreak >nul
|
|
goto waitloop
|
|
)
|
|
|
|
:: Small extra delay to ensure all file handles are released
|
|
timeout /t 2 /nobreak >nul
|
|
|
|
:: Remove current app folder
|
|
if exist "${runningAppWin}" (
|
|
rmdir /s /q "${runningAppWin}"
|
|
)
|
|
|
|
:: Move new app to current location
|
|
move "${newAppWin}" "${runningAppWin}"
|
|
|
|
:: Clean up extraction directory
|
|
rmdir /s /q "${extractionDirWin}" 2>nul
|
|
|
|
:: Launch the new app
|
|
start "" "${launcherPathWin}"
|
|
|
|
:: Clean up scheduled tasks starting with ElectrobunUpdate_
|
|
for /f "tokens=1" %%t in ('schtasks /query /fo list ^| findstr /i "ElectrobunUpdate_"') do (
|
|
schtasks /delete /tn "%%t" /f >nul 2>&1
|
|
)
|
|
|
|
:: Delete this update script after a short delay
|
|
ping -n 2 127.0.0.1 >nul
|
|
del "%~f0"
|
|
`;
|
|
await Bun.write(updateScriptPath, updateScript);
|
|
const scriptPathWin = updateScriptPath.replace(/\//g, "\\");
|
|
const taskName = `ElectrobunUpdate_${Date.now()}`;
|
|
execSync(`schtasks /create /tn "${taskName}" /tr "cmd /c \\"${scriptPathWin}\\"" /sc once /st 00:00 /f`, { stdio: "ignore" });
|
|
execSync(`schtasks /run /tn "${taskName}"`, { stdio: "ignore" });
|
|
quit();
|
|
}
|
|
} catch (error) {
|
|
emitStatus("error", `Failed to replace app: ${error.message}`, {
|
|
errorMessage: error.message
|
|
});
|
|
console.error("Failed to replace app with new version", error);
|
|
return;
|
|
}
|
|
emitStatus("launching-new-version", "Launching updated version...");
|
|
if (OS === "macos") {
|
|
const pid = process.pid;
|
|
Bun.spawn([
|
|
"sh",
|
|
"-c",
|
|
`while kill -0 ${pid} 2>/dev/null; do sleep 0.5; done; sleep 1; open "${runningAppBundlePath}"`
|
|
], {
|
|
detached: true,
|
|
stdio: ["ignore", "ignore", "ignore"]
|
|
});
|
|
} else if (OS === "linux") {
|
|
const launcherPath = join2(runningAppBundlePath, "bin", "launcher");
|
|
Bun.spawn(["sh", "-c", `"${launcherPath}" &`], {
|
|
detached: true
|
|
});
|
|
}
|
|
emitStatus("complete", "Update complete, restarting application...");
|
|
quit();
|
|
}
|
|
}
|
|
},
|
|
channelBucketUrl: async () => {
|
|
await Updater.getLocallocalInfo();
|
|
return localInfo.baseUrl;
|
|
},
|
|
appDataFolder: async () => {
|
|
await Updater.getLocallocalInfo();
|
|
const appDataFolder = join2(getAppDataDir2(), localInfo.identifier, localInfo.channel);
|
|
return appDataFolder;
|
|
},
|
|
localInfo: {
|
|
version: async () => {
|
|
return (await Updater.getLocallocalInfo()).version;
|
|
},
|
|
hash: async () => {
|
|
return (await Updater.getLocallocalInfo()).hash;
|
|
},
|
|
channel: async () => {
|
|
return (await Updater.getLocallocalInfo()).channel;
|
|
},
|
|
baseUrl: async () => {
|
|
return (await Updater.getLocallocalInfo()).baseUrl;
|
|
}
|
|
},
|
|
getLocallocalInfo: async () => {
|
|
if (localInfo) {
|
|
return localInfo;
|
|
}
|
|
try {
|
|
const resourcesDir = "Resources";
|
|
localInfo = await Bun.file(`../${resourcesDir}/version.json`).json();
|
|
return localInfo;
|
|
} catch (error) {
|
|
console.error("Failed to read version.json", error);
|
|
throw error;
|
|
}
|
|
}
|
|
};
|
|
});
|
|
|
|
// node_modules/electrobun/dist/api/bun/core/BuildConfig.ts
|
|
var buildConfig = null, BuildConfig;
|
|
var init_BuildConfig = __esm(() => {
|
|
BuildConfig = {
|
|
get: async () => {
|
|
if (buildConfig) {
|
|
return buildConfig;
|
|
}
|
|
try {
|
|
const resourcesDir = "Resources";
|
|
buildConfig = await Bun.file(`../${resourcesDir}/build.json`).json();
|
|
return buildConfig;
|
|
} catch (error) {
|
|
buildConfig = {
|
|
defaultRenderer: "native",
|
|
availableRenderers: ["native"]
|
|
};
|
|
return buildConfig;
|
|
}
|
|
},
|
|
getCached: () => buildConfig
|
|
};
|
|
});
|
|
|
|
// node_modules/electrobun/dist/api/bun/core/Socket.ts
|
|
var exports_Socket = {};
|
|
__export(exports_Socket, {
|
|
socketMap: () => socketMap,
|
|
sendMessageToWebviewViaSocket: () => sendMessageToWebviewViaSocket,
|
|
rpcServer: () => rpcServer,
|
|
rpcPort: () => rpcPort
|
|
});
|
|
import { createCipheriv, createDecipheriv, randomBytes } from "crypto";
|
|
function base64ToUint8Array(base64) {
|
|
{
|
|
return new Uint8Array(atob(base64).split("").map((char) => char.charCodeAt(0)));
|
|
}
|
|
}
|
|
function encrypt(secretKey, text) {
|
|
const iv = new Uint8Array(randomBytes(12));
|
|
const cipher = createCipheriv("aes-256-gcm", secretKey, iv);
|
|
const encrypted = Buffer.concat([
|
|
new Uint8Array(cipher.update(text, "utf8")),
|
|
new Uint8Array(cipher.final())
|
|
]).toString("base64");
|
|
const tag = cipher.getAuthTag().toString("base64");
|
|
return { encrypted, iv: Buffer.from(iv).toString("base64"), tag };
|
|
}
|
|
function decrypt(secretKey, encryptedData, iv, tag) {
|
|
const decipher = createDecipheriv("aes-256-gcm", secretKey, iv);
|
|
decipher.setAuthTag(tag);
|
|
const decrypted = Buffer.concat([
|
|
new Uint8Array(decipher.update(encryptedData)),
|
|
new Uint8Array(decipher.final())
|
|
]);
|
|
return decrypted.toString("utf8");
|
|
}
|
|
var socketMap, startRPCServer = () => {
|
|
const startPort = 50000;
|
|
const endPort = 65535;
|
|
const payloadLimit = 1024 * 1024 * 500;
|
|
let port = startPort;
|
|
let server = null;
|
|
while (port <= endPort) {
|
|
try {
|
|
server = Bun.serve({
|
|
port,
|
|
fetch(req, server2) {
|
|
const url = new URL(req.url);
|
|
if (url.pathname === "/socket") {
|
|
const webviewIdString = url.searchParams.get("webviewId");
|
|
if (!webviewIdString) {
|
|
return new Response("Missing webviewId", { status: 400 });
|
|
}
|
|
const webviewId = parseInt(webviewIdString, 10);
|
|
const success = server2.upgrade(req, { data: { webviewId } });
|
|
return success ? undefined : new Response("Upgrade failed", { status: 500 });
|
|
}
|
|
console.log("unhandled RPC Server request", req.url);
|
|
},
|
|
websocket: {
|
|
idleTimeout: 960,
|
|
maxPayloadLength: payloadLimit,
|
|
backpressureLimit: payloadLimit * 2,
|
|
open(ws) {
|
|
if (!ws?.data) {
|
|
return;
|
|
}
|
|
const { webviewId } = ws.data;
|
|
if (!socketMap[webviewId]) {
|
|
socketMap[webviewId] = { socket: ws, queue: [] };
|
|
} else {
|
|
socketMap[webviewId].socket = ws;
|
|
}
|
|
},
|
|
close(ws, _code, _reason) {
|
|
if (!ws?.data) {
|
|
return;
|
|
}
|
|
const { webviewId } = ws.data;
|
|
if (socketMap[webviewId]) {
|
|
socketMap[webviewId].socket = null;
|
|
}
|
|
},
|
|
message(ws, message) {
|
|
if (!ws?.data) {
|
|
return;
|
|
}
|
|
const { webviewId } = ws.data;
|
|
const browserView = BrowserView.getById(webviewId);
|
|
if (!browserView) {
|
|
return;
|
|
}
|
|
if (browserView.rpcHandler) {
|
|
if (typeof message === "string") {
|
|
try {
|
|
const encryptedPacket = JSON.parse(message);
|
|
const decrypted = decrypt(browserView.secretKey, base64ToUint8Array(encryptedPacket.encryptedData), base64ToUint8Array(encryptedPacket.iv), base64ToUint8Array(encryptedPacket.tag));
|
|
browserView.rpcHandler(JSON.parse(decrypted));
|
|
} catch (error) {
|
|
console.log("Error handling message:", error);
|
|
}
|
|
} else if (message instanceof ArrayBuffer) {
|
|
console.log("TODO: Received ArrayBuffer message:", message);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
break;
|
|
} catch (error) {
|
|
if (error.code === "EADDRINUSE") {
|
|
console.log(`Port ${port} in use, trying next port...`);
|
|
port++;
|
|
} else {
|
|
throw error;
|
|
}
|
|
}
|
|
}
|
|
return { rpcServer: server, rpcPort: port };
|
|
}, rpcServer, rpcPort, sendMessageToWebviewViaSocket = (webviewId, message) => {
|
|
const rpc = socketMap[webviewId];
|
|
const browserView = BrowserView.getById(webviewId);
|
|
if (!browserView)
|
|
return false;
|
|
if (rpc?.socket?.readyState === WebSocket.OPEN) {
|
|
try {
|
|
const unencryptedString = JSON.stringify(message);
|
|
const encrypted = encrypt(browserView.secretKey, unencryptedString);
|
|
const encryptedPacket = {
|
|
encryptedData: encrypted.encrypted,
|
|
iv: encrypted.iv,
|
|
tag: encrypted.tag
|
|
};
|
|
const encryptedPacketString = JSON.stringify(encryptedPacket);
|
|
rpc.socket.send(encryptedPacketString);
|
|
return true;
|
|
} catch (error) {
|
|
console.error("Error sending message to webview via socket:", error);
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
var init_Socket = __esm(async () => {
|
|
await init_BrowserView();
|
|
socketMap = {};
|
|
({ rpcServer, rpcPort } = startRPCServer());
|
|
console.log("Server started at", rpcServer?.url.origin);
|
|
});
|
|
|
|
// node_modules/electrobun/dist/api/bun/core/BrowserView.ts
|
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
|
|
class BrowserView {
|
|
id = nextWebviewId++;
|
|
ptr;
|
|
hostWebviewId;
|
|
windowId;
|
|
renderer;
|
|
url = null;
|
|
html = null;
|
|
preload = null;
|
|
partition = null;
|
|
autoResize = true;
|
|
frame = {
|
|
x: 0,
|
|
y: 0,
|
|
width: 800,
|
|
height: 600
|
|
};
|
|
pipePrefix;
|
|
inStream;
|
|
outStream;
|
|
secretKey;
|
|
rpc;
|
|
rpcHandler;
|
|
navigationRules = null;
|
|
sandbox = false;
|
|
startTransparent = false;
|
|
startPassthrough = false;
|
|
constructor(options = defaultOptions) {
|
|
this.url = options.url || defaultOptions.url || null;
|
|
this.html = options.html || defaultOptions.html || null;
|
|
this.preload = options.preload || defaultOptions.preload || null;
|
|
this.frame = {
|
|
x: options.frame?.x ?? defaultOptions.frame.x,
|
|
y: options.frame?.y ?? defaultOptions.frame.y,
|
|
width: options.frame?.width ?? defaultOptions.frame.width,
|
|
height: options.frame?.height ?? defaultOptions.frame.height
|
|
};
|
|
this.rpc = options.rpc;
|
|
this.secretKey = new Uint8Array(randomBytes2(32));
|
|
this.partition = options.partition || null;
|
|
this.pipePrefix = `/private/tmp/electrobun_ipc_pipe_${hash}_${randomId}_${this.id}`;
|
|
this.hostWebviewId = options.hostWebviewId;
|
|
this.windowId = options.windowId ?? 0;
|
|
this.autoResize = options.autoResize === false ? false : true;
|
|
this.navigationRules = options.navigationRules || null;
|
|
this.renderer = options.renderer ?? defaultOptions.renderer ?? "native";
|
|
this.sandbox = options.sandbox ?? false;
|
|
this.startTransparent = options.startTransparent ?? false;
|
|
this.startPassthrough = options.startPassthrough ?? false;
|
|
BrowserViewMap[this.id] = this;
|
|
this.ptr = this.init();
|
|
if (this.html) {
|
|
console.log(`DEBUG: BrowserView constructor triggering loadHTML for webview ${this.id}`);
|
|
setTimeout(() => {
|
|
console.log(`DEBUG: BrowserView delayed loadHTML for webview ${this.id}`);
|
|
this.loadHTML(this.html);
|
|
}, 100);
|
|
} else {
|
|
console.log(`DEBUG: BrowserView constructor - no HTML provided for webview ${this.id}`);
|
|
}
|
|
}
|
|
init() {
|
|
this.createStreams();
|
|
return ffi.request.createWebview({
|
|
id: this.id,
|
|
windowId: this.windowId,
|
|
renderer: this.renderer,
|
|
rpcPort,
|
|
secretKey: this.secretKey.toString(),
|
|
hostWebviewId: this.hostWebviewId || null,
|
|
pipePrefix: this.pipePrefix,
|
|
partition: this.partition,
|
|
url: this.html ? null : this.url,
|
|
html: this.html,
|
|
preload: this.preload,
|
|
frame: {
|
|
width: this.frame.width,
|
|
height: this.frame.height,
|
|
x: this.frame.x,
|
|
y: this.frame.y
|
|
},
|
|
autoResize: this.autoResize,
|
|
navigationRules: this.navigationRules,
|
|
sandbox: this.sandbox,
|
|
startTransparent: this.startTransparent,
|
|
startPassthrough: this.startPassthrough
|
|
});
|
|
}
|
|
createStreams() {
|
|
if (!this.rpc) {
|
|
this.rpc = BrowserView.defineRPC({
|
|
handlers: { requests: {}, messages: {} }
|
|
});
|
|
}
|
|
this.rpc.setTransport(this.createTransport());
|
|
}
|
|
sendMessageToWebviewViaExecute(jsonMessage) {
|
|
const stringifiedMessage = typeof jsonMessage === "string" ? jsonMessage : JSON.stringify(jsonMessage);
|
|
const wrappedMessage = `window.__electrobun.receiveMessageFromBun(${stringifiedMessage})`;
|
|
this.executeJavascript(wrappedMessage);
|
|
}
|
|
sendInternalMessageViaExecute(jsonMessage) {
|
|
const stringifiedMessage = typeof jsonMessage === "string" ? jsonMessage : JSON.stringify(jsonMessage);
|
|
const wrappedMessage = `window.__electrobun.receiveInternalMessageFromBun(${stringifiedMessage})`;
|
|
this.executeJavascript(wrappedMessage);
|
|
}
|
|
executeJavascript(js) {
|
|
ffi.request.evaluateJavascriptWithNoCompletion({ id: this.id, js });
|
|
}
|
|
loadURL(url) {
|
|
console.log(`DEBUG: loadURL called for webview ${this.id}: ${url}`);
|
|
this.url = url;
|
|
native.symbols.loadURLInWebView(this.ptr, toCString(this.url));
|
|
}
|
|
loadHTML(html) {
|
|
this.html = html;
|
|
console.log(`DEBUG: Setting HTML content for webview ${this.id}:`, html.substring(0, 50) + "...");
|
|
if (this.renderer === "cef") {
|
|
native.symbols.setWebviewHTMLContent(this.id, toCString(html));
|
|
this.loadURL("views://internal/index.html");
|
|
} else {
|
|
native.symbols.loadHTMLInWebView(this.ptr, toCString(html));
|
|
}
|
|
}
|
|
setNavigationRules(rules) {
|
|
this.navigationRules = JSON.stringify(rules);
|
|
const rulesJson = JSON.stringify(rules);
|
|
native.symbols.setWebviewNavigationRules(this.ptr, toCString(rulesJson));
|
|
}
|
|
findInPage(searchText, options) {
|
|
const forward = options?.forward ?? true;
|
|
const matchCase = options?.matchCase ?? false;
|
|
native.symbols.webviewFindInPage(this.ptr, toCString(searchText), forward, matchCase);
|
|
}
|
|
stopFindInPage() {
|
|
native.symbols.webviewStopFind(this.ptr);
|
|
}
|
|
openDevTools() {
|
|
native.symbols.webviewOpenDevTools(this.ptr);
|
|
}
|
|
closeDevTools() {
|
|
native.symbols.webviewCloseDevTools(this.ptr);
|
|
}
|
|
toggleDevTools() {
|
|
native.symbols.webviewToggleDevTools(this.ptr);
|
|
}
|
|
on(name, handler) {
|
|
const specificName = `${name}-${this.id}`;
|
|
eventEmitter_default.on(specificName, handler);
|
|
}
|
|
createTransport = () => {
|
|
const that = this;
|
|
return {
|
|
send(message) {
|
|
const sentOverSocket = sendMessageToWebviewViaSocket(that.id, message);
|
|
if (!sentOverSocket) {
|
|
try {
|
|
const messageString = JSON.stringify(message);
|
|
that.sendMessageToWebviewViaExecute(messageString);
|
|
} catch (error) {
|
|
console.error("bun: failed to serialize message to webview", error);
|
|
}
|
|
}
|
|
},
|
|
registerHandler(handler) {
|
|
that.rpcHandler = handler;
|
|
}
|
|
};
|
|
};
|
|
remove() {
|
|
native.symbols.webviewRemove(this.ptr);
|
|
delete BrowserViewMap[this.id];
|
|
}
|
|
static getById(id) {
|
|
return BrowserViewMap[id];
|
|
}
|
|
static getAll() {
|
|
return Object.values(BrowserViewMap);
|
|
}
|
|
static defineRPC(config) {
|
|
return defineElectrobunRPC("bun", config);
|
|
}
|
|
}
|
|
var BrowserViewMap, nextWebviewId = 1, hash, buildConfig2, defaultOptions, randomId;
|
|
var init_BrowserView = __esm(async () => {
|
|
init_eventEmitter();
|
|
init_BuildConfig();
|
|
await __promiseAll([
|
|
init_native(),
|
|
init_Updater(),
|
|
init_Socket()
|
|
]);
|
|
BrowserViewMap = {};
|
|
hash = await Updater.localInfo.hash();
|
|
buildConfig2 = await BuildConfig.get();
|
|
defaultOptions = {
|
|
url: null,
|
|
html: null,
|
|
preload: null,
|
|
renderer: buildConfig2.defaultRenderer,
|
|
frame: {
|
|
x: 0,
|
|
y: 0,
|
|
width: 800,
|
|
height: 600
|
|
}
|
|
};
|
|
randomId = Math.random().toString(36).substring(7);
|
|
});
|
|
|
|
// node_modules/electrobun/dist/api/bun/core/Paths.ts
|
|
var exports_Paths = {};
|
|
__export(exports_Paths, {
|
|
VIEWS_FOLDER: () => VIEWS_FOLDER
|
|
});
|
|
import { resolve as resolve2 } from "path";
|
|
var RESOURCES_FOLDER, VIEWS_FOLDER;
|
|
var init_Paths = __esm(() => {
|
|
RESOURCES_FOLDER = resolve2("../Resources/");
|
|
VIEWS_FOLDER = resolve2(RESOURCES_FOLDER, "app/views");
|
|
});
|
|
|
|
// node_modules/electrobun/dist/api/bun/core/Tray.ts
|
|
import { join as join3 } from "path";
|
|
|
|
class Tray {
|
|
id = nextTrayId++;
|
|
ptr = null;
|
|
constructor({
|
|
title = "",
|
|
image = "",
|
|
template = true,
|
|
width = 16,
|
|
height = 16
|
|
} = {}) {
|
|
try {
|
|
this.ptr = ffi.request.createTray({
|
|
id: this.id,
|
|
title,
|
|
image: this.resolveImagePath(image),
|
|
template,
|
|
width,
|
|
height
|
|
});
|
|
} catch (error) {
|
|
console.warn("Tray creation failed:", error);
|
|
console.warn("System tray functionality may not be available on this platform");
|
|
this.ptr = null;
|
|
}
|
|
TrayMap[this.id] = this;
|
|
}
|
|
resolveImagePath(imgPath) {
|
|
if (imgPath.startsWith("views://")) {
|
|
return join3(VIEWS_FOLDER, imgPath.replace("views://", ""));
|
|
} else {
|
|
return imgPath;
|
|
}
|
|
}
|
|
setTitle(title) {
|
|
if (!this.ptr)
|
|
return;
|
|
ffi.request.setTrayTitle({ id: this.id, title });
|
|
}
|
|
setImage(imgPath) {
|
|
if (!this.ptr)
|
|
return;
|
|
ffi.request.setTrayImage({
|
|
id: this.id,
|
|
image: this.resolveImagePath(imgPath)
|
|
});
|
|
}
|
|
setMenu(menu) {
|
|
if (!this.ptr)
|
|
return;
|
|
const menuWithDefaults = menuConfigWithDefaults(menu);
|
|
ffi.request.setTrayMenu({
|
|
id: this.id,
|
|
menuConfig: JSON.stringify(menuWithDefaults)
|
|
});
|
|
}
|
|
on(name, handler) {
|
|
const specificName = `${name}-${this.id}`;
|
|
eventEmitter_default.on(specificName, handler);
|
|
}
|
|
remove() {
|
|
console.log("Tray.remove() called for id:", this.id);
|
|
if (this.ptr) {
|
|
ffi.request.removeTray({ id: this.id });
|
|
}
|
|
delete TrayMap[this.id];
|
|
console.log("Tray removed from TrayMap");
|
|
}
|
|
static getById(id) {
|
|
return TrayMap[id];
|
|
}
|
|
static getAll() {
|
|
return Object.values(TrayMap);
|
|
}
|
|
static removeById(id) {
|
|
const tray = TrayMap[id];
|
|
if (tray) {
|
|
tray.remove();
|
|
}
|
|
}
|
|
}
|
|
var nextTrayId = 1, TrayMap, menuConfigWithDefaults = (menu) => {
|
|
return menu.map((item) => {
|
|
if (item.type === "divider" || item.type === "separator") {
|
|
return { type: "divider" };
|
|
} else {
|
|
const menuItem = item;
|
|
const actionWithDataId = ffi.internal.serializeMenuAction(menuItem.action || "", menuItem.data);
|
|
return {
|
|
label: menuItem.label || "",
|
|
type: menuItem.type || "normal",
|
|
action: actionWithDataId,
|
|
enabled: menuItem.enabled === false ? false : true,
|
|
checked: Boolean(menuItem.checked),
|
|
hidden: Boolean(menuItem.hidden),
|
|
tooltip: menuItem.tooltip || undefined,
|
|
...menuItem.submenu ? { submenu: menuConfigWithDefaults(menuItem.submenu) } : {}
|
|
};
|
|
}
|
|
});
|
|
};
|
|
var init_Tray = __esm(async () => {
|
|
init_eventEmitter();
|
|
init_Paths();
|
|
await init_native();
|
|
TrayMap = {};
|
|
});
|
|
|
|
// node_modules/electrobun/dist/api/bun/preload/.generated/compiled.ts
|
|
var preloadScript = `(() => {
|
|
// src/bun/preload/encryption.ts
|
|
function base64ToUint8Array(base64) {
|
|
return new Uint8Array(atob(base64).split("").map((char) => char.charCodeAt(0)));
|
|
}
|
|
function uint8ArrayToBase64(uint8Array) {
|
|
let binary = "";
|
|
for (let i = 0;i < uint8Array.length; i++) {
|
|
binary += String.fromCharCode(uint8Array[i]);
|
|
}
|
|
return btoa(binary);
|
|
}
|
|
async function generateKeyFromBytes(rawKey) {
|
|
return await window.crypto.subtle.importKey("raw", rawKey, { name: "AES-GCM" }, true, ["encrypt", "decrypt"]);
|
|
}
|
|
async function initEncryption() {
|
|
const secretKey = await generateKeyFromBytes(new Uint8Array(window.__electrobunSecretKeyBytes));
|
|
const encryptString = async (plaintext) => {
|
|
const encoder = new TextEncoder;
|
|
const encodedText = encoder.encode(plaintext);
|
|
const iv = window.crypto.getRandomValues(new Uint8Array(12));
|
|
const encryptedBuffer = await window.crypto.subtle.encrypt({ name: "AES-GCM", iv }, secretKey, encodedText);
|
|
const encryptedData = new Uint8Array(encryptedBuffer.slice(0, -16));
|
|
const tag = new Uint8Array(encryptedBuffer.slice(-16));
|
|
return {
|
|
encryptedData: uint8ArrayToBase64(encryptedData),
|
|
iv: uint8ArrayToBase64(iv),
|
|
tag: uint8ArrayToBase64(tag)
|
|
};
|
|
};
|
|
const decryptString = async (encryptedDataB64, ivB64, tagB64) => {
|
|
const encryptedData = base64ToUint8Array(encryptedDataB64);
|
|
const iv = base64ToUint8Array(ivB64);
|
|
const tag = base64ToUint8Array(tagB64);
|
|
const combinedData = new Uint8Array(encryptedData.length + tag.length);
|
|
combinedData.set(encryptedData);
|
|
combinedData.set(tag, encryptedData.length);
|
|
const decryptedBuffer = await window.crypto.subtle.decrypt({ name: "AES-GCM", iv }, secretKey, combinedData);
|
|
const decoder = new TextDecoder;
|
|
return decoder.decode(decryptedBuffer);
|
|
};
|
|
window.__electrobun_encrypt = encryptString;
|
|
window.__electrobun_decrypt = decryptString;
|
|
}
|
|
|
|
// src/bun/preload/internalRpc.ts
|
|
var pendingRequests = {};
|
|
var requestId = 0;
|
|
var isProcessingQueue = false;
|
|
var sendQueue = [];
|
|
function processQueue() {
|
|
if (isProcessingQueue) {
|
|
setTimeout(processQueue);
|
|
return;
|
|
}
|
|
if (sendQueue.length === 0)
|
|
return;
|
|
isProcessingQueue = true;
|
|
const batch = JSON.stringify(sendQueue);
|
|
sendQueue.length = 0;
|
|
window.__electrobunInternalBridge?.postMessage(batch);
|
|
setTimeout(() => {
|
|
isProcessingQueue = false;
|
|
}, 2);
|
|
}
|
|
function send(type, payload) {
|
|
sendQueue.push(JSON.stringify({ type: "message", id: type, payload }));
|
|
processQueue();
|
|
}
|
|
function request(type, payload) {
|
|
return new Promise((resolve, reject) => {
|
|
const id = \`req_\${++requestId}_\${Date.now()}\`;
|
|
pendingRequests[id] = { resolve, reject };
|
|
sendQueue.push(JSON.stringify({
|
|
type: "request",
|
|
method: type,
|
|
id,
|
|
params: payload,
|
|
hostWebviewId: window.__electrobunWebviewId
|
|
}));
|
|
processQueue();
|
|
setTimeout(() => {
|
|
if (pendingRequests[id]) {
|
|
delete pendingRequests[id];
|
|
reject(new Error(\`Request timeout: \${type}\`));
|
|
}
|
|
}, 1e4);
|
|
});
|
|
}
|
|
function handleResponse(msg) {
|
|
if (msg && msg.type === "response" && msg.id) {
|
|
const pending = pendingRequests[msg.id];
|
|
if (pending) {
|
|
delete pendingRequests[msg.id];
|
|
if (msg.success)
|
|
pending.resolve(msg.payload);
|
|
else
|
|
pending.reject(msg.payload);
|
|
}
|
|
}
|
|
}
|
|
|
|
// src/bun/preload/dragRegions.ts
|
|
function isAppRegionDrag(e) {
|
|
const target = e.target;
|
|
if (!target || !target.closest)
|
|
return false;
|
|
const draggableByStyle = target.closest('[style*="app-region"][style*="drag"]');
|
|
const draggableByClass = target.closest(".electrobun-webkit-app-region-drag");
|
|
return !!(draggableByStyle || draggableByClass);
|
|
}
|
|
function initDragRegions() {
|
|
document.addEventListener("mousedown", (e) => {
|
|
if (isAppRegionDrag(e)) {
|
|
send("startWindowMove", { id: window.__electrobunWindowId });
|
|
}
|
|
});
|
|
document.addEventListener("mouseup", (e) => {
|
|
if (isAppRegionDrag(e)) {
|
|
send("stopWindowMove", { id: window.__electrobunWindowId });
|
|
}
|
|
});
|
|
}
|
|
|
|
// src/bun/preload/webviewTag.ts
|
|
var webviewRegistry = {};
|
|
|
|
class ElectrobunWebviewTag extends HTMLElement {
|
|
webviewId = null;
|
|
maskSelectors = new Set;
|
|
lastRect = { x: 0, y: 0, width: 0, height: 0 };
|
|
resizeObserver = null;
|
|
positionCheckLoop = null;
|
|
transparent = false;
|
|
passthroughEnabled = false;
|
|
hidden = false;
|
|
sandboxed = false;
|
|
_eventListeners = {};
|
|
constructor() {
|
|
super();
|
|
}
|
|
connectedCallback() {
|
|
requestAnimationFrame(() => this.initWebview());
|
|
}
|
|
disconnectedCallback() {
|
|
if (this.webviewId !== null) {
|
|
send("webviewTagRemove", { id: this.webviewId });
|
|
delete webviewRegistry[this.webviewId];
|
|
}
|
|
if (this.resizeObserver)
|
|
this.resizeObserver.disconnect();
|
|
if (this.positionCheckLoop)
|
|
clearInterval(this.positionCheckLoop);
|
|
}
|
|
async initWebview() {
|
|
const rect = this.getBoundingClientRect();
|
|
this.lastRect = {
|
|
x: rect.x,
|
|
y: rect.y,
|
|
width: rect.width,
|
|
height: rect.height
|
|
};
|
|
const url = this.getAttribute("src");
|
|
const html = this.getAttribute("html");
|
|
const preload = this.getAttribute("preload");
|
|
const partition = this.getAttribute("partition");
|
|
const renderer = this.getAttribute("renderer") || "native";
|
|
const masks = this.getAttribute("masks");
|
|
const sandbox = this.hasAttribute("sandbox");
|
|
this.sandboxed = sandbox;
|
|
const transparent = this.hasAttribute("transparent");
|
|
const passthrough = this.hasAttribute("passthrough");
|
|
this.transparent = transparent;
|
|
this.passthroughEnabled = passthrough;
|
|
if (transparent)
|
|
this.style.opacity = "0";
|
|
if (passthrough)
|
|
this.style.pointerEvents = "none";
|
|
if (masks) {
|
|
masks.split(",").forEach((s) => this.maskSelectors.add(s.trim()));
|
|
}
|
|
try {
|
|
const webviewId = await request("webviewTagInit", {
|
|
hostWebviewId: window.__electrobunWebviewId,
|
|
windowId: window.__electrobunWindowId,
|
|
renderer,
|
|
url,
|
|
html,
|
|
preload,
|
|
partition,
|
|
frame: {
|
|
width: rect.width,
|
|
height: rect.height,
|
|
x: rect.x,
|
|
y: rect.y
|
|
},
|
|
navigationRules: null,
|
|
sandbox,
|
|
transparent,
|
|
passthrough
|
|
});
|
|
this.webviewId = webviewId;
|
|
this.id = \`electrobun-webview-\${webviewId}\`;
|
|
webviewRegistry[webviewId] = this;
|
|
this.setupObservers();
|
|
this.syncDimensions(true);
|
|
requestAnimationFrame(() => {
|
|
Object.values(webviewRegistry).forEach((webview) => {
|
|
if (webview !== this && webview.webviewId !== null) {
|
|
webview.syncDimensions(true);
|
|
}
|
|
});
|
|
});
|
|
} catch (err) {
|
|
console.error("Failed to init webview:", err);
|
|
}
|
|
}
|
|
setupObservers() {
|
|
this.resizeObserver = new ResizeObserver(() => this.syncDimensions());
|
|
this.resizeObserver.observe(this);
|
|
this.positionCheckLoop = setInterval(() => this.syncDimensions(), 100);
|
|
}
|
|
syncDimensions(force = false) {
|
|
if (this.webviewId === null)
|
|
return;
|
|
const rect = this.getBoundingClientRect();
|
|
const newRect = {
|
|
x: rect.x,
|
|
y: rect.y,
|
|
width: rect.width,
|
|
height: rect.height
|
|
};
|
|
if (newRect.width === 0 && newRect.height === 0) {
|
|
return;
|
|
}
|
|
if (!force && newRect.x === this.lastRect.x && newRect.y === this.lastRect.y && newRect.width === this.lastRect.width && newRect.height === this.lastRect.height) {
|
|
return;
|
|
}
|
|
this.lastRect = newRect;
|
|
const masks = [];
|
|
this.maskSelectors.forEach((selector) => {
|
|
try {
|
|
document.querySelectorAll(selector).forEach((el) => {
|
|
const mr = el.getBoundingClientRect();
|
|
masks.push({
|
|
x: mr.x - rect.x,
|
|
y: mr.y - rect.y,
|
|
width: mr.width,
|
|
height: mr.height
|
|
});
|
|
});
|
|
} catch (_e) {}
|
|
});
|
|
send("webviewTagResize", {
|
|
id: this.webviewId,
|
|
frame: newRect,
|
|
masks: JSON.stringify(masks)
|
|
});
|
|
}
|
|
loadURL(url) {
|
|
if (this.webviewId === null)
|
|
return;
|
|
this.setAttribute("src", url);
|
|
send("webviewTagUpdateSrc", { id: this.webviewId, url });
|
|
}
|
|
loadHTML(html) {
|
|
if (this.webviewId === null)
|
|
return;
|
|
send("webviewTagUpdateHtml", { id: this.webviewId, html });
|
|
}
|
|
reload() {
|
|
if (this.webviewId !== null)
|
|
send("webviewTagReload", { id: this.webviewId });
|
|
}
|
|
goBack() {
|
|
if (this.webviewId !== null)
|
|
send("webviewTagGoBack", { id: this.webviewId });
|
|
}
|
|
goForward() {
|
|
if (this.webviewId !== null)
|
|
send("webviewTagGoForward", { id: this.webviewId });
|
|
}
|
|
async canGoBack() {
|
|
if (this.webviewId === null)
|
|
return false;
|
|
return await request("webviewTagCanGoBack", {
|
|
id: this.webviewId
|
|
});
|
|
}
|
|
async canGoForward() {
|
|
if (this.webviewId === null)
|
|
return false;
|
|
return await request("webviewTagCanGoForward", {
|
|
id: this.webviewId
|
|
});
|
|
}
|
|
toggleTransparent(value) {
|
|
if (this.webviewId === null)
|
|
return;
|
|
this.transparent = value !== undefined ? value : !this.transparent;
|
|
this.style.opacity = this.transparent ? "0" : "";
|
|
send("webviewTagSetTransparent", {
|
|
id: this.webviewId,
|
|
transparent: this.transparent
|
|
});
|
|
}
|
|
togglePassthrough(value) {
|
|
if (this.webviewId === null)
|
|
return;
|
|
this.passthroughEnabled = value !== undefined ? value : !this.passthroughEnabled;
|
|
this.style.pointerEvents = this.passthroughEnabled ? "none" : "";
|
|
send("webviewTagSetPassthrough", {
|
|
id: this.webviewId,
|
|
enablePassthrough: this.passthroughEnabled
|
|
});
|
|
}
|
|
toggleHidden(value) {
|
|
if (this.webviewId === null)
|
|
return;
|
|
this.hidden = value !== undefined ? value : !this.hidden;
|
|
send("webviewTagSetHidden", { id: this.webviewId, hidden: this.hidden });
|
|
}
|
|
addMaskSelector(selector) {
|
|
this.maskSelectors.add(selector);
|
|
this.syncDimensions(true);
|
|
}
|
|
removeMaskSelector(selector) {
|
|
this.maskSelectors.delete(selector);
|
|
this.syncDimensions(true);
|
|
}
|
|
setNavigationRules(rules) {
|
|
if (this.webviewId !== null) {
|
|
send("webviewTagSetNavigationRules", { id: this.webviewId, rules });
|
|
}
|
|
}
|
|
findInPage(searchText, options) {
|
|
if (this.webviewId === null)
|
|
return;
|
|
const forward = options?.forward !== false;
|
|
const matchCase = options?.matchCase || false;
|
|
send("webviewTagFindInPage", {
|
|
id: this.webviewId,
|
|
searchText,
|
|
forward,
|
|
matchCase
|
|
});
|
|
}
|
|
stopFindInPage() {
|
|
if (this.webviewId !== null)
|
|
send("webviewTagStopFind", { id: this.webviewId });
|
|
}
|
|
openDevTools() {
|
|
if (this.webviewId !== null)
|
|
send("webviewTagOpenDevTools", { id: this.webviewId });
|
|
}
|
|
closeDevTools() {
|
|
if (this.webviewId !== null)
|
|
send("webviewTagCloseDevTools", { id: this.webviewId });
|
|
}
|
|
toggleDevTools() {
|
|
if (this.webviewId !== null)
|
|
send("webviewTagToggleDevTools", { id: this.webviewId });
|
|
}
|
|
on(event, listener) {
|
|
if (!this._eventListeners[event])
|
|
this._eventListeners[event] = [];
|
|
this._eventListeners[event].push(listener);
|
|
}
|
|
off(event, listener) {
|
|
if (!this._eventListeners[event])
|
|
return;
|
|
const idx = this._eventListeners[event].indexOf(listener);
|
|
if (idx !== -1)
|
|
this._eventListeners[event].splice(idx, 1);
|
|
}
|
|
emit(event, detail) {
|
|
const listeners = this._eventListeners[event];
|
|
if (listeners) {
|
|
const customEvent = new CustomEvent(event, { detail });
|
|
listeners.forEach((fn) => fn(customEvent));
|
|
}
|
|
}
|
|
get src() {
|
|
return this.getAttribute("src");
|
|
}
|
|
set src(value) {
|
|
if (value) {
|
|
this.setAttribute("src", value);
|
|
if (this.webviewId !== null)
|
|
this.loadURL(value);
|
|
} else {
|
|
this.removeAttribute("src");
|
|
}
|
|
}
|
|
get html() {
|
|
return this.getAttribute("html");
|
|
}
|
|
set html(value) {
|
|
if (value) {
|
|
this.setAttribute("html", value);
|
|
if (this.webviewId !== null)
|
|
this.loadHTML(value);
|
|
} else {
|
|
this.removeAttribute("html");
|
|
}
|
|
}
|
|
get preload() {
|
|
return this.getAttribute("preload");
|
|
}
|
|
set preload(value) {
|
|
if (value)
|
|
this.setAttribute("preload", value);
|
|
else
|
|
this.removeAttribute("preload");
|
|
}
|
|
get renderer() {
|
|
return this.getAttribute("renderer") || "native";
|
|
}
|
|
set renderer(value) {
|
|
this.setAttribute("renderer", value);
|
|
}
|
|
get sandbox() {
|
|
return this.sandboxed;
|
|
}
|
|
}
|
|
function initWebviewTag() {
|
|
if (!customElements.get("electrobun-webview")) {
|
|
customElements.define("electrobun-webview", ElectrobunWebviewTag);
|
|
}
|
|
const injectStyles = () => {
|
|
const style = document.createElement("style");
|
|
style.textContent = \`
|
|
electrobun-webview {
|
|
display: block;
|
|
width: 800px;
|
|
height: 300px;
|
|
background: #fff;
|
|
background-repeat: no-repeat !important;
|
|
overflow: hidden;
|
|
}
|
|
\`;
|
|
if (document.head?.firstChild) {
|
|
document.head.insertBefore(style, document.head.firstChild);
|
|
} else if (document.head) {
|
|
document.head.appendChild(style);
|
|
}
|
|
};
|
|
if (document.head) {
|
|
injectStyles();
|
|
} else {
|
|
document.addEventListener("DOMContentLoaded", injectStyles);
|
|
}
|
|
}
|
|
|
|
// src/bun/preload/events.ts
|
|
function emitWebviewEvent(eventName, detail) {
|
|
setTimeout(() => {
|
|
const bridge = window.__electrobunEventBridge || window.__electrobunInternalBridge;
|
|
bridge?.postMessage(JSON.stringify({
|
|
id: "webviewEvent",
|
|
type: "message",
|
|
payload: {
|
|
id: window.__electrobunWebviewId,
|
|
eventName,
|
|
detail
|
|
}
|
|
}));
|
|
});
|
|
}
|
|
function initLifecycleEvents() {
|
|
window.addEventListener("load", () => {
|
|
if (window === window.top) {
|
|
emitWebviewEvent("dom-ready", document.location.href);
|
|
}
|
|
});
|
|
window.addEventListener("popstate", () => {
|
|
emitWebviewEvent("did-navigate-in-page", window.location.href);
|
|
});
|
|
window.addEventListener("hashchange", () => {
|
|
emitWebviewEvent("did-navigate-in-page", window.location.href);
|
|
});
|
|
}
|
|
var cmdKeyHeld = false;
|
|
var cmdKeyTimestamp = 0;
|
|
var CMD_KEY_THRESHOLD_MS = 500;
|
|
function isCmdHeld() {
|
|
if (cmdKeyHeld)
|
|
return true;
|
|
return Date.now() - cmdKeyTimestamp < CMD_KEY_THRESHOLD_MS && cmdKeyTimestamp > 0;
|
|
}
|
|
function initCmdClickHandling() {
|
|
window.addEventListener("keydown", (event) => {
|
|
if (event.key === "Meta" || event.metaKey) {
|
|
cmdKeyHeld = true;
|
|
cmdKeyTimestamp = Date.now();
|
|
}
|
|
}, true);
|
|
window.addEventListener("keyup", (event) => {
|
|
if (event.key === "Meta") {
|
|
cmdKeyHeld = false;
|
|
cmdKeyTimestamp = Date.now();
|
|
}
|
|
}, true);
|
|
window.addEventListener("blur", () => {
|
|
cmdKeyHeld = false;
|
|
});
|
|
window.addEventListener("click", (event) => {
|
|
if (event.metaKey || event.ctrlKey) {
|
|
const anchor = event.target?.closest?.("a");
|
|
if (anchor && anchor.href) {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
event.stopImmediatePropagation();
|
|
emitWebviewEvent("new-window-open", JSON.stringify({
|
|
url: anchor.href,
|
|
isCmdClick: true,
|
|
isSPANavigation: false
|
|
}));
|
|
}
|
|
}
|
|
}, true);
|
|
}
|
|
function initSPANavigationInterception() {
|
|
const originalPushState = history.pushState;
|
|
const originalReplaceState = history.replaceState;
|
|
history.pushState = function(state, title, url) {
|
|
if (isCmdHeld() && url) {
|
|
const resolvedUrl = new URL(String(url), window.location.href).href;
|
|
emitWebviewEvent("new-window-open", JSON.stringify({
|
|
url: resolvedUrl,
|
|
isCmdClick: true,
|
|
isSPANavigation: true
|
|
}));
|
|
return;
|
|
}
|
|
return originalPushState.apply(this, [state, title, url]);
|
|
};
|
|
history.replaceState = function(state, title, url) {
|
|
if (isCmdHeld() && url) {
|
|
const resolvedUrl = new URL(String(url), window.location.href).href;
|
|
emitWebviewEvent("new-window-open", JSON.stringify({
|
|
url: resolvedUrl,
|
|
isCmdClick: true,
|
|
isSPANavigation: true
|
|
}));
|
|
return;
|
|
}
|
|
return originalReplaceState.apply(this, [state, title, url]);
|
|
};
|
|
}
|
|
function initOverscrollPrevention() {
|
|
document.addEventListener("DOMContentLoaded", () => {
|
|
const style = document.createElement("style");
|
|
style.type = "text/css";
|
|
style.appendChild(document.createTextNode("html, body { overscroll-behavior: none; }"));
|
|
document.head.appendChild(style);
|
|
});
|
|
}
|
|
|
|
// src/bun/preload/index.ts
|
|
initEncryption().catch((err) => console.error("Failed to initialize encryption:", err));
|
|
var internalMessageHandler = (msg) => {
|
|
handleResponse(msg);
|
|
};
|
|
if (!window.__electrobun) {
|
|
window.__electrobun = {
|
|
receiveInternalMessageFromBun: internalMessageHandler,
|
|
receiveMessageFromBun: (msg) => {
|
|
console.log("receiveMessageFromBun (no handler):", msg);
|
|
}
|
|
};
|
|
} else {
|
|
window.__electrobun.receiveInternalMessageFromBun = internalMessageHandler;
|
|
window.__electrobun.receiveMessageFromBun = (msg) => {
|
|
console.log("receiveMessageFromBun (no handler):", msg);
|
|
};
|
|
}
|
|
window.__electrobunSendToHost = (message) => {
|
|
emitWebviewEvent("host-message", JSON.stringify(message));
|
|
};
|
|
initLifecycleEvents();
|
|
initCmdClickHandling();
|
|
initSPANavigationInterception();
|
|
initOverscrollPrevention();
|
|
initDragRegions();
|
|
initWebviewTag();
|
|
})();
|
|
`, preloadScriptSandboxed = `(() => {
|
|
// src/bun/preload/events.ts
|
|
function emitWebviewEvent(eventName, detail) {
|
|
setTimeout(() => {
|
|
const bridge = window.__electrobunEventBridge || window.__electrobunInternalBridge;
|
|
bridge?.postMessage(JSON.stringify({
|
|
id: "webviewEvent",
|
|
type: "message",
|
|
payload: {
|
|
id: window.__electrobunWebviewId,
|
|
eventName,
|
|
detail
|
|
}
|
|
}));
|
|
});
|
|
}
|
|
function initLifecycleEvents() {
|
|
window.addEventListener("load", () => {
|
|
if (window === window.top) {
|
|
emitWebviewEvent("dom-ready", document.location.href);
|
|
}
|
|
});
|
|
window.addEventListener("popstate", () => {
|
|
emitWebviewEvent("did-navigate-in-page", window.location.href);
|
|
});
|
|
window.addEventListener("hashchange", () => {
|
|
emitWebviewEvent("did-navigate-in-page", window.location.href);
|
|
});
|
|
}
|
|
var cmdKeyHeld = false;
|
|
var cmdKeyTimestamp = 0;
|
|
var CMD_KEY_THRESHOLD_MS = 500;
|
|
function isCmdHeld() {
|
|
if (cmdKeyHeld)
|
|
return true;
|
|
return Date.now() - cmdKeyTimestamp < CMD_KEY_THRESHOLD_MS && cmdKeyTimestamp > 0;
|
|
}
|
|
function initCmdClickHandling() {
|
|
window.addEventListener("keydown", (event) => {
|
|
if (event.key === "Meta" || event.metaKey) {
|
|
cmdKeyHeld = true;
|
|
cmdKeyTimestamp = Date.now();
|
|
}
|
|
}, true);
|
|
window.addEventListener("keyup", (event) => {
|
|
if (event.key === "Meta") {
|
|
cmdKeyHeld = false;
|
|
cmdKeyTimestamp = Date.now();
|
|
}
|
|
}, true);
|
|
window.addEventListener("blur", () => {
|
|
cmdKeyHeld = false;
|
|
});
|
|
window.addEventListener("click", (event) => {
|
|
if (event.metaKey || event.ctrlKey) {
|
|
const anchor = event.target?.closest?.("a");
|
|
if (anchor && anchor.href) {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
event.stopImmediatePropagation();
|
|
emitWebviewEvent("new-window-open", JSON.stringify({
|
|
url: anchor.href,
|
|
isCmdClick: true,
|
|
isSPANavigation: false
|
|
}));
|
|
}
|
|
}
|
|
}, true);
|
|
}
|
|
function initSPANavigationInterception() {
|
|
const originalPushState = history.pushState;
|
|
const originalReplaceState = history.replaceState;
|
|
history.pushState = function(state, title, url) {
|
|
if (isCmdHeld() && url) {
|
|
const resolvedUrl = new URL(String(url), window.location.href).href;
|
|
emitWebviewEvent("new-window-open", JSON.stringify({
|
|
url: resolvedUrl,
|
|
isCmdClick: true,
|
|
isSPANavigation: true
|
|
}));
|
|
return;
|
|
}
|
|
return originalPushState.apply(this, [state, title, url]);
|
|
};
|
|
history.replaceState = function(state, title, url) {
|
|
if (isCmdHeld() && url) {
|
|
const resolvedUrl = new URL(String(url), window.location.href).href;
|
|
emitWebviewEvent("new-window-open", JSON.stringify({
|
|
url: resolvedUrl,
|
|
isCmdClick: true,
|
|
isSPANavigation: true
|
|
}));
|
|
return;
|
|
}
|
|
return originalReplaceState.apply(this, [state, title, url]);
|
|
};
|
|
}
|
|
function initOverscrollPrevention() {
|
|
document.addEventListener("DOMContentLoaded", () => {
|
|
const style = document.createElement("style");
|
|
style.type = "text/css";
|
|
style.appendChild(document.createTextNode("html, body { overscroll-behavior: none; }"));
|
|
document.head.appendChild(style);
|
|
});
|
|
}
|
|
|
|
// src/bun/preload/index-sandboxed.ts
|
|
initLifecycleEvents();
|
|
initCmdClickHandling();
|
|
initSPANavigationInterception();
|
|
initOverscrollPrevention();
|
|
})();
|
|
`;
|
|
|
|
// node_modules/electrobun/dist/api/bun/proc/native.ts
|
|
import { join as join4 } from "path";
|
|
import {
|
|
dlopen,
|
|
suffix,
|
|
JSCallback,
|
|
CString,
|
|
ptr,
|
|
FFIType,
|
|
toArrayBuffer
|
|
} from "bun:ffi";
|
|
function storeMenuData(data) {
|
|
const id = `menuData_${++menuDataCounter}`;
|
|
menuDataRegistry.set(id, data);
|
|
return id;
|
|
}
|
|
function getMenuData(id) {
|
|
return menuDataRegistry.get(id);
|
|
}
|
|
function clearMenuData(id) {
|
|
menuDataRegistry.delete(id);
|
|
}
|
|
function serializeMenuAction(action, data) {
|
|
const dataId = storeMenuData(data);
|
|
return `${ELECTROBUN_DELIMITER}${dataId}|${action}`;
|
|
}
|
|
function deserializeMenuAction(encodedAction) {
|
|
let actualAction = encodedAction;
|
|
let data = undefined;
|
|
if (encodedAction.startsWith(ELECTROBUN_DELIMITER)) {
|
|
const parts = encodedAction.split("|");
|
|
if (parts.length >= 4) {
|
|
const dataId = parts[2];
|
|
actualAction = parts.slice(3).join("|");
|
|
data = getMenuData(dataId);
|
|
clearMenuData(dataId);
|
|
}
|
|
}
|
|
return { action: actualAction, data };
|
|
}
|
|
|
|
class SessionCookies {
|
|
partitionId;
|
|
constructor(partitionId) {
|
|
this.partitionId = partitionId;
|
|
}
|
|
get(filter) {
|
|
const filterJson = JSON.stringify(filter || {});
|
|
const result = native.symbols.sessionGetCookies(toCString(this.partitionId), toCString(filterJson));
|
|
if (!result)
|
|
return [];
|
|
try {
|
|
return JSON.parse(result.toString());
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
set(cookie) {
|
|
const cookieJson = JSON.stringify(cookie);
|
|
return native.symbols.sessionSetCookie(toCString(this.partitionId), toCString(cookieJson));
|
|
}
|
|
remove(url, name) {
|
|
return native.symbols.sessionRemoveCookie(toCString(this.partitionId), toCString(url), toCString(name));
|
|
}
|
|
clear() {
|
|
native.symbols.sessionClearCookies(toCString(this.partitionId));
|
|
}
|
|
}
|
|
|
|
class SessionInstance {
|
|
partition;
|
|
cookies;
|
|
constructor(partition) {
|
|
this.partition = partition;
|
|
this.cookies = new SessionCookies(partition);
|
|
}
|
|
clearStorageData(types = "all") {
|
|
const typesArray = types === "all" ? ["all"] : types;
|
|
native.symbols.sessionClearStorageData(toCString(this.partition), toCString(JSON.stringify(typesArray)));
|
|
}
|
|
}
|
|
function toCString(jsString, addNullTerminator = true) {
|
|
let appendWith = "";
|
|
if (addNullTerminator && !jsString.endsWith("\x00")) {
|
|
appendWith = "\x00";
|
|
}
|
|
const buff = Buffer.from(jsString + appendWith, "utf8");
|
|
return ptr(buff);
|
|
}
|
|
var menuDataRegistry, menuDataCounter = 0, ELECTROBUN_DELIMITER = "|EB|", native, ffi, windowCloseCallback, windowMoveCallback, windowResizeCallback, windowFocusCallback, getMimeType, getHTMLForWebviewSync, urlOpenCallback, quitRequestedCallback, globalShortcutHandlers, globalShortcutCallback, GlobalShortcut, Screen, sessionCache, Session, webviewDecideNavigation, webviewEventHandler = (id, eventName, detail) => {
|
|
const webview = BrowserView.getById(id);
|
|
if (!webview) {
|
|
console.error("[webviewEventHandler] No webview found for id:", id);
|
|
return;
|
|
}
|
|
if (webview.hostWebviewId) {
|
|
const hostWebview = BrowserView.getById(webview.hostWebviewId);
|
|
if (!hostWebview) {
|
|
console.error("[webviewEventHandler] No webview found for id:", id);
|
|
return;
|
|
}
|
|
let js;
|
|
if (eventName === "new-window-open" || eventName === "host-message") {
|
|
js = `document.querySelector('#electrobun-webview-${id}').emit(${JSON.stringify(eventName)}, ${detail});`;
|
|
} else {
|
|
js = `document.querySelector('#electrobun-webview-${id}').emit(${JSON.stringify(eventName)}, ${JSON.stringify(detail)});`;
|
|
}
|
|
native.symbols.evaluateJavaScriptWithNoCompletion(hostWebview.ptr, toCString(js));
|
|
}
|
|
const eventMap = {
|
|
"will-navigate": "willNavigate",
|
|
"did-navigate": "didNavigate",
|
|
"did-navigate-in-page": "didNavigateInPage",
|
|
"did-commit-navigation": "didCommitNavigation",
|
|
"dom-ready": "domReady",
|
|
"new-window-open": "newWindowOpen",
|
|
"host-message": "hostMessage",
|
|
"download-started": "downloadStarted",
|
|
"download-progress": "downloadProgress",
|
|
"download-completed": "downloadCompleted",
|
|
"download-failed": "downloadFailed",
|
|
"load-started": "loadStarted",
|
|
"load-committed": "loadCommitted",
|
|
"load-finished": "loadFinished"
|
|
};
|
|
const mappedName = eventMap[eventName];
|
|
const handler = mappedName ? eventEmitter_default.events.webview[mappedName] : undefined;
|
|
if (!handler) {
|
|
return { success: false };
|
|
}
|
|
let parsedDetail = detail;
|
|
if (eventName === "new-window-open" || eventName === "host-message" || eventName === "download-started" || eventName === "download-progress" || eventName === "download-completed" || eventName === "download-failed") {
|
|
try {
|
|
parsedDetail = JSON.parse(detail);
|
|
} catch (e) {
|
|
console.error("[webviewEventHandler] Failed to parse JSON:", e);
|
|
parsedDetail = detail;
|
|
}
|
|
}
|
|
const event = handler({
|
|
detail: parsedDetail
|
|
});
|
|
eventEmitter_default.emitEvent(event);
|
|
eventEmitter_default.emitEvent(event, id);
|
|
}, webviewEventJSCallback, bunBridgePostmessageHandler, eventBridgeHandler, internalBridgeHandler, trayItemHandler, applicationMenuHandler, contextMenuHandler, internalRpcHandlers;
|
|
var init_native = __esm(async () => {
|
|
init_eventEmitter();
|
|
await __promiseAll([
|
|
init_BrowserView(),
|
|
init_Tray(),
|
|
init_BrowserWindow()
|
|
]);
|
|
menuDataRegistry = new Map;
|
|
native = (() => {
|
|
try {
|
|
const nativeWrapperPath = join4(process.cwd(), `libNativeWrapper.${suffix}`);
|
|
return dlopen(nativeWrapperPath, {
|
|
createWindowWithFrameAndStyleFromWorker: {
|
|
args: [
|
|
FFIType.u32,
|
|
FFIType.f64,
|
|
FFIType.f64,
|
|
FFIType.f64,
|
|
FFIType.f64,
|
|
FFIType.u32,
|
|
FFIType.cstring,
|
|
FFIType.bool,
|
|
FFIType.function,
|
|
FFIType.function,
|
|
FFIType.function,
|
|
FFIType.function
|
|
],
|
|
returns: FFIType.ptr
|
|
},
|
|
setWindowTitle: {
|
|
args: [
|
|
FFIType.ptr,
|
|
FFIType.cstring
|
|
],
|
|
returns: FFIType.void
|
|
},
|
|
showWindow: {
|
|
args: [
|
|
FFIType.ptr
|
|
],
|
|
returns: FFIType.void
|
|
},
|
|
closeWindow: {
|
|
args: [
|
|
FFIType.ptr
|
|
],
|
|
returns: FFIType.void
|
|
},
|
|
minimizeWindow: {
|
|
args: [FFIType.ptr],
|
|
returns: FFIType.void
|
|
},
|
|
restoreWindow: {
|
|
args: [FFIType.ptr],
|
|
returns: FFIType.void
|
|
},
|
|
isWindowMinimized: {
|
|
args: [FFIType.ptr],
|
|
returns: FFIType.bool
|
|
},
|
|
maximizeWindow: {
|
|
args: [FFIType.ptr],
|
|
returns: FFIType.void
|
|
},
|
|
unmaximizeWindow: {
|
|
args: [FFIType.ptr],
|
|
returns: FFIType.void
|
|
},
|
|
isWindowMaximized: {
|
|
args: [FFIType.ptr],
|
|
returns: FFIType.bool
|
|
},
|
|
setWindowFullScreen: {
|
|
args: [FFIType.ptr, FFIType.bool],
|
|
returns: FFIType.void
|
|
},
|
|
isWindowFullScreen: {
|
|
args: [FFIType.ptr],
|
|
returns: FFIType.bool
|
|
},
|
|
setWindowAlwaysOnTop: {
|
|
args: [FFIType.ptr, FFIType.bool],
|
|
returns: FFIType.void
|
|
},
|
|
isWindowAlwaysOnTop: {
|
|
args: [FFIType.ptr],
|
|
returns: FFIType.bool
|
|
},
|
|
setWindowPosition: {
|
|
args: [FFIType.ptr, FFIType.f64, FFIType.f64],
|
|
returns: FFIType.void
|
|
},
|
|
setWindowSize: {
|
|
args: [FFIType.ptr, FFIType.f64, FFIType.f64],
|
|
returns: FFIType.void
|
|
},
|
|
setWindowFrame: {
|
|
args: [FFIType.ptr, FFIType.f64, FFIType.f64, FFIType.f64, FFIType.f64],
|
|
returns: FFIType.void
|
|
},
|
|
getWindowFrame: {
|
|
args: [FFIType.ptr, FFIType.ptr, FFIType.ptr, FFIType.ptr, FFIType.ptr],
|
|
returns: FFIType.void
|
|
},
|
|
initWebview: {
|
|
args: [
|
|
FFIType.u32,
|
|
FFIType.ptr,
|
|
FFIType.cstring,
|
|
FFIType.cstring,
|
|
FFIType.f64,
|
|
FFIType.f64,
|
|
FFIType.f64,
|
|
FFIType.f64,
|
|
FFIType.bool,
|
|
FFIType.cstring,
|
|
FFIType.function,
|
|
FFIType.function,
|
|
FFIType.function,
|
|
FFIType.function,
|
|
FFIType.function,
|
|
FFIType.cstring,
|
|
FFIType.cstring,
|
|
FFIType.bool,
|
|
FFIType.bool
|
|
],
|
|
returns: FFIType.ptr
|
|
},
|
|
setNextWebviewFlags: {
|
|
args: [
|
|
FFIType.bool,
|
|
FFIType.bool
|
|
],
|
|
returns: FFIType.void
|
|
},
|
|
webviewCanGoBack: {
|
|
args: [FFIType.ptr],
|
|
returns: FFIType.bool
|
|
},
|
|
webviewCanGoForward: {
|
|
args: [FFIType.ptr],
|
|
returns: FFIType.bool
|
|
},
|
|
resizeWebview: {
|
|
args: [
|
|
FFIType.ptr,
|
|
FFIType.f64,
|
|
FFIType.f64,
|
|
FFIType.f64,
|
|
FFIType.f64,
|
|
FFIType.cstring
|
|
],
|
|
returns: FFIType.void
|
|
},
|
|
loadURLInWebView: {
|
|
args: [FFIType.ptr, FFIType.cstring],
|
|
returns: FFIType.void
|
|
},
|
|
loadHTMLInWebView: {
|
|
args: [FFIType.ptr, FFIType.cstring],
|
|
returns: FFIType.void
|
|
},
|
|
updatePreloadScriptToWebView: {
|
|
args: [
|
|
FFIType.ptr,
|
|
FFIType.cstring,
|
|
FFIType.cstring,
|
|
FFIType.bool
|
|
],
|
|
returns: FFIType.void
|
|
},
|
|
webviewGoBack: {
|
|
args: [FFIType.ptr],
|
|
returns: FFIType.void
|
|
},
|
|
webviewGoForward: {
|
|
args: [FFIType.ptr],
|
|
returns: FFIType.void
|
|
},
|
|
webviewReload: {
|
|
args: [FFIType.ptr],
|
|
returns: FFIType.void
|
|
},
|
|
webviewRemove: {
|
|
args: [FFIType.ptr],
|
|
returns: FFIType.void
|
|
},
|
|
setWebviewHTMLContent: {
|
|
args: [FFIType.u32, FFIType.cstring],
|
|
returns: FFIType.void
|
|
},
|
|
startWindowMove: {
|
|
args: [FFIType.ptr],
|
|
returns: FFIType.void
|
|
},
|
|
stopWindowMove: {
|
|
args: [],
|
|
returns: FFIType.void
|
|
},
|
|
webviewSetTransparent: {
|
|
args: [FFIType.ptr, FFIType.bool],
|
|
returns: FFIType.void
|
|
},
|
|
webviewSetPassthrough: {
|
|
args: [FFIType.ptr, FFIType.bool],
|
|
returns: FFIType.void
|
|
},
|
|
webviewSetHidden: {
|
|
args: [FFIType.ptr, FFIType.bool],
|
|
returns: FFIType.void
|
|
},
|
|
setWebviewNavigationRules: {
|
|
args: [FFIType.ptr, FFIType.cstring],
|
|
returns: FFIType.void
|
|
},
|
|
webviewFindInPage: {
|
|
args: [FFIType.ptr, FFIType.cstring, FFIType.bool, FFIType.bool],
|
|
returns: FFIType.void
|
|
},
|
|
webviewStopFind: {
|
|
args: [FFIType.ptr],
|
|
returns: FFIType.void
|
|
},
|
|
evaluateJavaScriptWithNoCompletion: {
|
|
args: [FFIType.ptr, FFIType.cstring],
|
|
returns: FFIType.void
|
|
},
|
|
webviewOpenDevTools: {
|
|
args: [FFIType.ptr],
|
|
returns: FFIType.void
|
|
},
|
|
webviewCloseDevTools: {
|
|
args: [FFIType.ptr],
|
|
returns: FFIType.void
|
|
},
|
|
webviewToggleDevTools: {
|
|
args: [FFIType.ptr],
|
|
returns: FFIType.void
|
|
},
|
|
createTray: {
|
|
args: [
|
|
FFIType.u32,
|
|
FFIType.cstring,
|
|
FFIType.cstring,
|
|
FFIType.bool,
|
|
FFIType.u32,
|
|
FFIType.u32,
|
|
FFIType.function
|
|
],
|
|
returns: FFIType.ptr
|
|
},
|
|
setTrayTitle: {
|
|
args: [FFIType.ptr, FFIType.cstring],
|
|
returns: FFIType.void
|
|
},
|
|
setTrayImage: {
|
|
args: [FFIType.ptr, FFIType.cstring],
|
|
returns: FFIType.void
|
|
},
|
|
setTrayMenu: {
|
|
args: [FFIType.ptr, FFIType.cstring],
|
|
returns: FFIType.void
|
|
},
|
|
removeTray: {
|
|
args: [FFIType.ptr],
|
|
returns: FFIType.void
|
|
},
|
|
setApplicationMenu: {
|
|
args: [FFIType.cstring, FFIType.function],
|
|
returns: FFIType.void
|
|
},
|
|
showContextMenu: {
|
|
args: [FFIType.cstring, FFIType.function],
|
|
returns: FFIType.void
|
|
},
|
|
moveToTrash: {
|
|
args: [FFIType.cstring],
|
|
returns: FFIType.bool
|
|
},
|
|
showItemInFolder: {
|
|
args: [FFIType.cstring],
|
|
returns: FFIType.void
|
|
},
|
|
openExternal: {
|
|
args: [FFIType.cstring],
|
|
returns: FFIType.bool
|
|
},
|
|
openPath: {
|
|
args: [FFIType.cstring],
|
|
returns: FFIType.bool
|
|
},
|
|
showNotification: {
|
|
args: [
|
|
FFIType.cstring,
|
|
FFIType.cstring,
|
|
FFIType.cstring,
|
|
FFIType.bool
|
|
],
|
|
returns: FFIType.void
|
|
},
|
|
setGlobalShortcutCallback: {
|
|
args: [FFIType.function],
|
|
returns: FFIType.void
|
|
},
|
|
registerGlobalShortcut: {
|
|
args: [FFIType.cstring],
|
|
returns: FFIType.bool
|
|
},
|
|
unregisterGlobalShortcut: {
|
|
args: [FFIType.cstring],
|
|
returns: FFIType.bool
|
|
},
|
|
unregisterAllGlobalShortcuts: {
|
|
args: [],
|
|
returns: FFIType.void
|
|
},
|
|
isGlobalShortcutRegistered: {
|
|
args: [FFIType.cstring],
|
|
returns: FFIType.bool
|
|
},
|
|
getAllDisplays: {
|
|
args: [],
|
|
returns: FFIType.cstring
|
|
},
|
|
getPrimaryDisplay: {
|
|
args: [],
|
|
returns: FFIType.cstring
|
|
},
|
|
getCursorScreenPoint: {
|
|
args: [],
|
|
returns: FFIType.cstring
|
|
},
|
|
openFileDialog: {
|
|
args: [
|
|
FFIType.cstring,
|
|
FFIType.cstring,
|
|
FFIType.int,
|
|
FFIType.int,
|
|
FFIType.int
|
|
],
|
|
returns: FFIType.cstring
|
|
},
|
|
showMessageBox: {
|
|
args: [
|
|
FFIType.cstring,
|
|
FFIType.cstring,
|
|
FFIType.cstring,
|
|
FFIType.cstring,
|
|
FFIType.cstring,
|
|
FFIType.int,
|
|
FFIType.int
|
|
],
|
|
returns: FFIType.int
|
|
},
|
|
clipboardReadText: {
|
|
args: [],
|
|
returns: FFIType.cstring
|
|
},
|
|
clipboardWriteText: {
|
|
args: [FFIType.cstring],
|
|
returns: FFIType.void
|
|
},
|
|
clipboardReadImage: {
|
|
args: [FFIType.ptr],
|
|
returns: FFIType.ptr
|
|
},
|
|
clipboardWriteImage: {
|
|
args: [FFIType.ptr, FFIType.u64],
|
|
returns: FFIType.void
|
|
},
|
|
clipboardClear: {
|
|
args: [],
|
|
returns: FFIType.void
|
|
},
|
|
clipboardAvailableFormats: {
|
|
args: [],
|
|
returns: FFIType.cstring
|
|
},
|
|
sessionGetCookies: {
|
|
args: [FFIType.cstring, FFIType.cstring],
|
|
returns: FFIType.cstring
|
|
},
|
|
sessionSetCookie: {
|
|
args: [FFIType.cstring, FFIType.cstring],
|
|
returns: FFIType.bool
|
|
},
|
|
sessionRemoveCookie: {
|
|
args: [FFIType.cstring, FFIType.cstring, FFIType.cstring],
|
|
returns: FFIType.bool
|
|
},
|
|
sessionClearCookies: {
|
|
args: [FFIType.cstring],
|
|
returns: FFIType.void
|
|
},
|
|
sessionClearStorageData: {
|
|
args: [FFIType.cstring, FFIType.cstring],
|
|
returns: FFIType.void
|
|
},
|
|
setURLOpenHandler: {
|
|
args: [FFIType.function],
|
|
returns: FFIType.void
|
|
},
|
|
getWindowStyle: {
|
|
args: [
|
|
FFIType.bool,
|
|
FFIType.bool,
|
|
FFIType.bool,
|
|
FFIType.bool,
|
|
FFIType.bool,
|
|
FFIType.bool,
|
|
FFIType.bool,
|
|
FFIType.bool,
|
|
FFIType.bool,
|
|
FFIType.bool,
|
|
FFIType.bool,
|
|
FFIType.bool
|
|
],
|
|
returns: FFIType.u32
|
|
},
|
|
setJSUtils: {
|
|
args: [
|
|
FFIType.function,
|
|
FFIType.function
|
|
],
|
|
returns: FFIType.void
|
|
},
|
|
setWindowIcon: {
|
|
args: [
|
|
FFIType.ptr,
|
|
FFIType.cstring
|
|
],
|
|
returns: FFIType.void
|
|
},
|
|
killApp: {
|
|
args: [],
|
|
returns: FFIType.void
|
|
},
|
|
stopEventLoop: {
|
|
args: [],
|
|
returns: FFIType.void
|
|
},
|
|
waitForShutdownComplete: {
|
|
args: [FFIType.i32],
|
|
returns: FFIType.void
|
|
},
|
|
forceExit: {
|
|
args: [FFIType.i32],
|
|
returns: FFIType.void
|
|
},
|
|
setQuitRequestedHandler: {
|
|
args: [FFIType.function],
|
|
returns: FFIType.void
|
|
},
|
|
testFFI2: {
|
|
args: [FFIType.function],
|
|
returns: FFIType.void
|
|
}
|
|
});
|
|
} catch (err) {
|
|
console.log("FATAL Error opening native FFI:", err.message);
|
|
console.log("This may be due to:");
|
|
console.log(" - Missing libNativeWrapper.dll/so/dylib");
|
|
console.log(" - Architecture mismatch (ARM64 vs x64)");
|
|
console.log(" - Missing WebView2 or CEF dependencies");
|
|
if (suffix === "so") {
|
|
console.log(" - Missing system libraries (try: ldd ./libNativeWrapper.so)");
|
|
}
|
|
console.log("Check that the build process completed successfully for your architecture.");
|
|
process.exit();
|
|
}
|
|
})();
|
|
ffi = {
|
|
request: {
|
|
createWindow: (params) => {
|
|
const {
|
|
id,
|
|
url: _url,
|
|
title,
|
|
frame: { x, y, width, height },
|
|
styleMask: {
|
|
Borderless,
|
|
Titled,
|
|
Closable,
|
|
Miniaturizable,
|
|
Resizable,
|
|
UnifiedTitleAndToolbar,
|
|
FullScreen,
|
|
FullSizeContentView,
|
|
UtilityWindow,
|
|
DocModalWindow,
|
|
NonactivatingPanel,
|
|
HUDWindow
|
|
},
|
|
titleBarStyle,
|
|
transparent
|
|
} = params;
|
|
const styleMask = native.symbols.getWindowStyle(Borderless, Titled, Closable, Miniaturizable, Resizable, UnifiedTitleAndToolbar, FullScreen, FullSizeContentView, UtilityWindow, DocModalWindow, NonactivatingPanel, HUDWindow);
|
|
const windowPtr = native.symbols.createWindowWithFrameAndStyleFromWorker(id, x, y, width, height, styleMask, toCString(titleBarStyle), transparent, windowCloseCallback, windowMoveCallback, windowResizeCallback, windowFocusCallback);
|
|
if (!windowPtr) {
|
|
throw "Failed to create window";
|
|
}
|
|
native.symbols.setWindowTitle(windowPtr, toCString(title));
|
|
native.symbols.showWindow(windowPtr);
|
|
return windowPtr;
|
|
},
|
|
setTitle: (params) => {
|
|
const { winId, title } = params;
|
|
const windowPtr = BrowserWindow.getById(winId)?.ptr;
|
|
if (!windowPtr) {
|
|
throw `Can't add webview to window. window no longer exists`;
|
|
}
|
|
native.symbols.setWindowTitle(windowPtr, toCString(title));
|
|
},
|
|
closeWindow: (params) => {
|
|
const { winId } = params;
|
|
const windowPtr = BrowserWindow.getById(winId)?.ptr;
|
|
if (!windowPtr) {
|
|
throw `Can't close window. Window no longer exists`;
|
|
}
|
|
native.symbols.closeWindow(windowPtr);
|
|
},
|
|
focusWindow: (params) => {
|
|
const { winId } = params;
|
|
const windowPtr = BrowserWindow.getById(winId)?.ptr;
|
|
if (!windowPtr) {
|
|
throw `Can't focus window. Window no longer exists`;
|
|
}
|
|
native.symbols.showWindow(windowPtr);
|
|
},
|
|
minimizeWindow: (params) => {
|
|
const { winId } = params;
|
|
const windowPtr = BrowserWindow.getById(winId)?.ptr;
|
|
if (!windowPtr) {
|
|
throw `Can't minimize window. Window no longer exists`;
|
|
}
|
|
native.symbols.minimizeWindow(windowPtr);
|
|
},
|
|
restoreWindow: (params) => {
|
|
const { winId } = params;
|
|
const windowPtr = BrowserWindow.getById(winId)?.ptr;
|
|
if (!windowPtr) {
|
|
throw `Can't restore window. Window no longer exists`;
|
|
}
|
|
native.symbols.restoreWindow(windowPtr);
|
|
},
|
|
isWindowMinimized: (params) => {
|
|
const { winId } = params;
|
|
const windowPtr = BrowserWindow.getById(winId)?.ptr;
|
|
if (!windowPtr) {
|
|
return false;
|
|
}
|
|
return native.symbols.isWindowMinimized(windowPtr);
|
|
},
|
|
maximizeWindow: (params) => {
|
|
const { winId } = params;
|
|
const windowPtr = BrowserWindow.getById(winId)?.ptr;
|
|
if (!windowPtr) {
|
|
throw `Can't maximize window. Window no longer exists`;
|
|
}
|
|
native.symbols.maximizeWindow(windowPtr);
|
|
},
|
|
unmaximizeWindow: (params) => {
|
|
const { winId } = params;
|
|
const windowPtr = BrowserWindow.getById(winId)?.ptr;
|
|
if (!windowPtr) {
|
|
throw `Can't unmaximize window. Window no longer exists`;
|
|
}
|
|
native.symbols.unmaximizeWindow(windowPtr);
|
|
},
|
|
isWindowMaximized: (params) => {
|
|
const { winId } = params;
|
|
const windowPtr = BrowserWindow.getById(winId)?.ptr;
|
|
if (!windowPtr) {
|
|
return false;
|
|
}
|
|
return native.symbols.isWindowMaximized(windowPtr);
|
|
},
|
|
setWindowFullScreen: (params) => {
|
|
const { winId, fullScreen } = params;
|
|
const windowPtr = BrowserWindow.getById(winId)?.ptr;
|
|
if (!windowPtr) {
|
|
throw `Can't set fullscreen. Window no longer exists`;
|
|
}
|
|
native.symbols.setWindowFullScreen(windowPtr, fullScreen);
|
|
},
|
|
isWindowFullScreen: (params) => {
|
|
const { winId } = params;
|
|
const windowPtr = BrowserWindow.getById(winId)?.ptr;
|
|
if (!windowPtr) {
|
|
return false;
|
|
}
|
|
return native.symbols.isWindowFullScreen(windowPtr);
|
|
},
|
|
setWindowAlwaysOnTop: (params) => {
|
|
const { winId, alwaysOnTop } = params;
|
|
const windowPtr = BrowserWindow.getById(winId)?.ptr;
|
|
if (!windowPtr) {
|
|
throw `Can't set always on top. Window no longer exists`;
|
|
}
|
|
native.symbols.setWindowAlwaysOnTop(windowPtr, alwaysOnTop);
|
|
},
|
|
isWindowAlwaysOnTop: (params) => {
|
|
const { winId } = params;
|
|
const windowPtr = BrowserWindow.getById(winId)?.ptr;
|
|
if (!windowPtr) {
|
|
return false;
|
|
}
|
|
return native.symbols.isWindowAlwaysOnTop(windowPtr);
|
|
},
|
|
setWindowPosition: (params) => {
|
|
const { winId, x, y } = params;
|
|
const windowPtr = BrowserWindow.getById(winId)?.ptr;
|
|
if (!windowPtr) {
|
|
throw `Can't set window position. Window no longer exists`;
|
|
}
|
|
native.symbols.setWindowPosition(windowPtr, x, y);
|
|
},
|
|
setWindowSize: (params) => {
|
|
const { winId, width, height } = params;
|
|
const windowPtr = BrowserWindow.getById(winId)?.ptr;
|
|
if (!windowPtr) {
|
|
throw `Can't set window size. Window no longer exists`;
|
|
}
|
|
native.symbols.setWindowSize(windowPtr, width, height);
|
|
},
|
|
setWindowFrame: (params) => {
|
|
const { winId, x, y, width, height } = params;
|
|
const windowPtr = BrowserWindow.getById(winId)?.ptr;
|
|
if (!windowPtr) {
|
|
throw `Can't set window frame. Window no longer exists`;
|
|
}
|
|
native.symbols.setWindowFrame(windowPtr, x, y, width, height);
|
|
},
|
|
getWindowFrame: (params) => {
|
|
const { winId } = params;
|
|
const windowPtr = BrowserWindow.getById(winId)?.ptr;
|
|
if (!windowPtr) {
|
|
return { x: 0, y: 0, width: 0, height: 0 };
|
|
}
|
|
const xBuf = new Float64Array(1);
|
|
const yBuf = new Float64Array(1);
|
|
const widthBuf = new Float64Array(1);
|
|
const heightBuf = new Float64Array(1);
|
|
native.symbols.getWindowFrame(windowPtr, ptr(xBuf), ptr(yBuf), ptr(widthBuf), ptr(heightBuf));
|
|
return {
|
|
x: xBuf[0],
|
|
y: yBuf[0],
|
|
width: widthBuf[0],
|
|
height: heightBuf[0]
|
|
};
|
|
},
|
|
createWebview: (params) => {
|
|
const {
|
|
id,
|
|
windowId,
|
|
renderer,
|
|
rpcPort: rpcPort2,
|
|
secretKey,
|
|
url,
|
|
partition,
|
|
preload,
|
|
frame: { x, y, width, height },
|
|
autoResize,
|
|
sandbox,
|
|
startTransparent,
|
|
startPassthrough
|
|
} = params;
|
|
const parentWindow = BrowserWindow.getById(windowId);
|
|
const windowPtr = parentWindow?.ptr;
|
|
const transparent = parentWindow?.transparent ?? false;
|
|
if (!windowPtr) {
|
|
throw `Can't add webview to window. window no longer exists`;
|
|
}
|
|
let dynamicPreload;
|
|
let selectedPreloadScript;
|
|
if (sandbox) {
|
|
dynamicPreload = `
|
|
window.__electrobunWebviewId = ${id};
|
|
window.__electrobunWindowId = ${windowId};
|
|
window.__electrobunEventBridge = window.__electrobunEventBridge || window.webkit?.messageHandlers?.eventBridge || window.eventBridge || window.chrome?.webview?.hostObjects?.eventBridge;
|
|
window.__electrobunInternalBridge = window.__electrobunInternalBridge || window.webkit?.messageHandlers?.internalBridge || window.internalBridge || window.chrome?.webview?.hostObjects?.internalBridge;
|
|
`;
|
|
selectedPreloadScript = preloadScriptSandboxed;
|
|
} else {
|
|
dynamicPreload = `
|
|
window.__electrobunWebviewId = ${id};
|
|
window.__electrobunWindowId = ${windowId};
|
|
window.__electrobunRpcSocketPort = ${rpcPort2};
|
|
window.__electrobunSecretKeyBytes = [${secretKey}];
|
|
window.__electrobunEventBridge = window.__electrobunEventBridge || window.webkit?.messageHandlers?.eventBridge || window.eventBridge || window.chrome?.webview?.hostObjects?.eventBridge;
|
|
window.__electrobunInternalBridge = window.__electrobunInternalBridge || window.webkit?.messageHandlers?.internalBridge || window.internalBridge || window.chrome?.webview?.hostObjects?.internalBridge;
|
|
window.__electrobunBunBridge = window.__electrobunBunBridge || window.webkit?.messageHandlers?.bunBridge || window.bunBridge || window.chrome?.webview?.hostObjects?.bunBridge;
|
|
`;
|
|
selectedPreloadScript = preloadScript;
|
|
}
|
|
const electrobunPreload = dynamicPreload + selectedPreloadScript;
|
|
const customPreload = preload;
|
|
native.symbols.setNextWebviewFlags(startTransparent, startPassthrough);
|
|
const webviewPtr = native.symbols.initWebview(id, windowPtr, toCString(renderer), toCString(url || ""), x, y, width, height, autoResize, toCString(partition || "persist:default"), webviewDecideNavigation, webviewEventJSCallback, eventBridgeHandler, bunBridgePostmessageHandler, internalBridgeHandler, toCString(electrobunPreload), toCString(customPreload || ""), transparent, sandbox);
|
|
if (!webviewPtr) {
|
|
throw "Failed to create webview";
|
|
}
|
|
return webviewPtr;
|
|
},
|
|
evaluateJavascriptWithNoCompletion: (params) => {
|
|
const { id, js } = params;
|
|
const webview = BrowserView.getById(id);
|
|
if (!webview?.ptr) {
|
|
return;
|
|
}
|
|
native.symbols.evaluateJavaScriptWithNoCompletion(webview.ptr, toCString(js));
|
|
},
|
|
createTray: (params) => {
|
|
const { id, title, image, template, width, height } = params;
|
|
const trayPtr = native.symbols.createTray(id, toCString(title), toCString(image), template, width, height, trayItemHandler);
|
|
if (!trayPtr) {
|
|
throw "Failed to create tray";
|
|
}
|
|
return trayPtr;
|
|
},
|
|
setTrayTitle: (params) => {
|
|
const { id, title } = params;
|
|
const tray = Tray.getById(id);
|
|
if (!tray)
|
|
return;
|
|
native.symbols.setTrayTitle(tray.ptr, toCString(title));
|
|
},
|
|
setTrayImage: (params) => {
|
|
const { id, image } = params;
|
|
const tray = Tray.getById(id);
|
|
if (!tray)
|
|
return;
|
|
native.symbols.setTrayImage(tray.ptr, toCString(image));
|
|
},
|
|
setTrayMenu: (params) => {
|
|
const { id, menuConfig } = params;
|
|
const tray = Tray.getById(id);
|
|
if (!tray)
|
|
return;
|
|
native.symbols.setTrayMenu(tray.ptr, toCString(menuConfig));
|
|
},
|
|
removeTray: (params) => {
|
|
const { id } = params;
|
|
const tray = Tray.getById(id);
|
|
if (!tray) {
|
|
throw `Can't remove tray. Tray no longer exists`;
|
|
}
|
|
native.symbols.removeTray(tray.ptr);
|
|
},
|
|
setApplicationMenu: (params) => {
|
|
const { menuConfig } = params;
|
|
native.symbols.setApplicationMenu(toCString(menuConfig), applicationMenuHandler);
|
|
},
|
|
showContextMenu: (params) => {
|
|
const { menuConfig } = params;
|
|
native.symbols.showContextMenu(toCString(menuConfig), contextMenuHandler);
|
|
},
|
|
moveToTrash: (params) => {
|
|
const { path } = params;
|
|
return native.symbols.moveToTrash(toCString(path));
|
|
},
|
|
showItemInFolder: (params) => {
|
|
const { path } = params;
|
|
native.symbols.showItemInFolder(toCString(path));
|
|
},
|
|
openExternal: (params) => {
|
|
const { url } = params;
|
|
return native.symbols.openExternal(toCString(url));
|
|
},
|
|
openPath: (params) => {
|
|
const { path } = params;
|
|
return native.symbols.openPath(toCString(path));
|
|
},
|
|
showNotification: (params) => {
|
|
const { title, body = "", subtitle = "", silent = false } = params;
|
|
native.symbols.showNotification(toCString(title), toCString(body), toCString(subtitle), silent);
|
|
},
|
|
openFileDialog: (params) => {
|
|
const {
|
|
startingFolder,
|
|
allowedFileTypes,
|
|
canChooseFiles,
|
|
canChooseDirectory,
|
|
allowsMultipleSelection
|
|
} = params;
|
|
const filePath = native.symbols.openFileDialog(toCString(startingFolder), toCString(allowedFileTypes), canChooseFiles ? 1 : 0, canChooseDirectory ? 1 : 0, allowsMultipleSelection ? 1 : 0);
|
|
return filePath.toString();
|
|
},
|
|
showMessageBox: (params) => {
|
|
const {
|
|
type = "info",
|
|
title = "",
|
|
message = "",
|
|
detail = "",
|
|
buttons = ["OK"],
|
|
defaultId = 0,
|
|
cancelId = -1
|
|
} = params;
|
|
const buttonsStr = buttons.join(",");
|
|
return native.symbols.showMessageBox(toCString(type), toCString(title), toCString(message), toCString(detail), toCString(buttonsStr), defaultId, cancelId);
|
|
},
|
|
clipboardReadText: () => {
|
|
const result = native.symbols.clipboardReadText();
|
|
if (!result)
|
|
return null;
|
|
return result.toString();
|
|
},
|
|
clipboardWriteText: (params) => {
|
|
native.symbols.clipboardWriteText(toCString(params.text));
|
|
},
|
|
clipboardReadImage: () => {
|
|
const sizeBuffer = new BigUint64Array(1);
|
|
const dataPtr = native.symbols.clipboardReadImage(ptr(sizeBuffer));
|
|
if (!dataPtr)
|
|
return null;
|
|
const size = Number(sizeBuffer[0]);
|
|
if (size === 0)
|
|
return null;
|
|
const result = new Uint8Array(size);
|
|
const sourceView = new Uint8Array(toArrayBuffer(dataPtr, 0, size));
|
|
result.set(sourceView);
|
|
return result;
|
|
},
|
|
clipboardWriteImage: (params) => {
|
|
const { pngData } = params;
|
|
native.symbols.clipboardWriteImage(ptr(pngData), BigInt(pngData.length));
|
|
},
|
|
clipboardClear: () => {
|
|
native.symbols.clipboardClear();
|
|
},
|
|
clipboardAvailableFormats: () => {
|
|
const result = native.symbols.clipboardAvailableFormats();
|
|
if (!result)
|
|
return [];
|
|
const formatsStr = result.toString();
|
|
if (!formatsStr)
|
|
return [];
|
|
return formatsStr.split(",").filter((f) => f.length > 0);
|
|
}
|
|
},
|
|
internal: {
|
|
storeMenuData,
|
|
getMenuData,
|
|
clearMenuData,
|
|
serializeMenuAction,
|
|
deserializeMenuAction
|
|
}
|
|
};
|
|
process.on("uncaughtException", (err) => {
|
|
console.error("Uncaught exception in worker:", err);
|
|
native.symbols.stopEventLoop();
|
|
native.symbols.waitForShutdownComplete(5000);
|
|
native.symbols.forceExit(1);
|
|
});
|
|
process.on("unhandledRejection", (reason, _promise) => {
|
|
console.error("Unhandled rejection in worker:", reason);
|
|
});
|
|
process.on("SIGINT", () => {
|
|
console.log("[electrobun] Received SIGINT, running quit sequence...");
|
|
const { quit: quit2 } = (init_Utils(), __toCommonJS(exports_Utils));
|
|
quit2();
|
|
});
|
|
process.on("SIGTERM", () => {
|
|
console.log("[electrobun] Received SIGTERM, running quit sequence...");
|
|
const { quit: quit2 } = (init_Utils(), __toCommonJS(exports_Utils));
|
|
quit2();
|
|
});
|
|
windowCloseCallback = new JSCallback((id) => {
|
|
const handler = eventEmitter_default.events.window.close;
|
|
const event = handler({
|
|
id
|
|
});
|
|
eventEmitter_default.emitEvent(event, id);
|
|
eventEmitter_default.emitEvent(event);
|
|
}, {
|
|
args: ["u32"],
|
|
returns: "void",
|
|
threadsafe: true
|
|
});
|
|
windowMoveCallback = new JSCallback((id, x, y) => {
|
|
const handler = eventEmitter_default.events.window.move;
|
|
const event = handler({
|
|
id,
|
|
x,
|
|
y
|
|
});
|
|
eventEmitter_default.emitEvent(event);
|
|
eventEmitter_default.emitEvent(event, id);
|
|
}, {
|
|
args: ["u32", "f64", "f64"],
|
|
returns: "void",
|
|
threadsafe: true
|
|
});
|
|
windowResizeCallback = new JSCallback((id, x, y, width, height) => {
|
|
const handler = eventEmitter_default.events.window.resize;
|
|
const event = handler({
|
|
id,
|
|
x,
|
|
y,
|
|
width,
|
|
height
|
|
});
|
|
eventEmitter_default.emitEvent(event);
|
|
eventEmitter_default.emitEvent(event, id);
|
|
}, {
|
|
args: ["u32", "f64", "f64", "f64", "f64"],
|
|
returns: "void",
|
|
threadsafe: true
|
|
});
|
|
windowFocusCallback = new JSCallback((id) => {
|
|
const handler = eventEmitter_default.events.window.focus;
|
|
const event = handler({
|
|
id
|
|
});
|
|
eventEmitter_default.emitEvent(event);
|
|
eventEmitter_default.emitEvent(event, id);
|
|
}, {
|
|
args: ["u32"],
|
|
returns: "void",
|
|
threadsafe: true
|
|
});
|
|
getMimeType = new JSCallback((filePath) => {
|
|
const _filePath = new CString(filePath).toString();
|
|
const mimeType = Bun.file(_filePath).type;
|
|
return toCString(mimeType.split(";")[0]);
|
|
}, {
|
|
args: [FFIType.cstring],
|
|
returns: FFIType.cstring
|
|
});
|
|
getHTMLForWebviewSync = new JSCallback((webviewId) => {
|
|
const webview = BrowserView.getById(webviewId);
|
|
return toCString(webview?.html || "");
|
|
}, {
|
|
args: [FFIType.u32],
|
|
returns: FFIType.cstring
|
|
});
|
|
native.symbols.setJSUtils(getMimeType, getHTMLForWebviewSync);
|
|
urlOpenCallback = new JSCallback((urlPtr) => {
|
|
const url = new CString(urlPtr).toString();
|
|
const handler = eventEmitter_default.events.app.openUrl;
|
|
const event = handler({ url });
|
|
eventEmitter_default.emitEvent(event);
|
|
}, {
|
|
args: [FFIType.cstring],
|
|
returns: "void",
|
|
threadsafe: true
|
|
});
|
|
if (process.platform === "darwin") {
|
|
native.symbols.setURLOpenHandler(urlOpenCallback);
|
|
}
|
|
quitRequestedCallback = new JSCallback(() => {
|
|
const { quit: quit2 } = (init_Utils(), __toCommonJS(exports_Utils));
|
|
quit2();
|
|
}, {
|
|
args: [],
|
|
returns: "void",
|
|
threadsafe: true
|
|
});
|
|
native.symbols.setQuitRequestedHandler(quitRequestedCallback);
|
|
globalShortcutHandlers = new Map;
|
|
globalShortcutCallback = new JSCallback((acceleratorPtr) => {
|
|
const accelerator = new CString(acceleratorPtr).toString();
|
|
const handler = globalShortcutHandlers.get(accelerator);
|
|
if (handler) {
|
|
handler();
|
|
}
|
|
}, {
|
|
args: [FFIType.cstring],
|
|
returns: "void",
|
|
threadsafe: true
|
|
});
|
|
native.symbols.setGlobalShortcutCallback(globalShortcutCallback);
|
|
GlobalShortcut = {
|
|
register: (accelerator, callback) => {
|
|
if (globalShortcutHandlers.has(accelerator)) {
|
|
return false;
|
|
}
|
|
const result = native.symbols.registerGlobalShortcut(toCString(accelerator));
|
|
if (result) {
|
|
globalShortcutHandlers.set(accelerator, callback);
|
|
}
|
|
return result;
|
|
},
|
|
unregister: (accelerator) => {
|
|
const result = native.symbols.unregisterGlobalShortcut(toCString(accelerator));
|
|
if (result) {
|
|
globalShortcutHandlers.delete(accelerator);
|
|
}
|
|
return result;
|
|
},
|
|
unregisterAll: () => {
|
|
native.symbols.unregisterAllGlobalShortcuts();
|
|
globalShortcutHandlers.clear();
|
|
},
|
|
isRegistered: (accelerator) => {
|
|
return native.symbols.isGlobalShortcutRegistered(toCString(accelerator));
|
|
}
|
|
};
|
|
Screen = {
|
|
getPrimaryDisplay: () => {
|
|
const jsonStr = native.symbols.getPrimaryDisplay();
|
|
if (!jsonStr) {
|
|
return {
|
|
id: 0,
|
|
bounds: { x: 0, y: 0, width: 0, height: 0 },
|
|
workArea: { x: 0, y: 0, width: 0, height: 0 },
|
|
scaleFactor: 1,
|
|
isPrimary: true
|
|
};
|
|
}
|
|
try {
|
|
return JSON.parse(jsonStr.toString());
|
|
} catch {
|
|
return {
|
|
id: 0,
|
|
bounds: { x: 0, y: 0, width: 0, height: 0 },
|
|
workArea: { x: 0, y: 0, width: 0, height: 0 },
|
|
scaleFactor: 1,
|
|
isPrimary: true
|
|
};
|
|
}
|
|
},
|
|
getAllDisplays: () => {
|
|
const jsonStr = native.symbols.getAllDisplays();
|
|
if (!jsonStr) {
|
|
return [];
|
|
}
|
|
try {
|
|
return JSON.parse(jsonStr.toString());
|
|
} catch {
|
|
return [];
|
|
}
|
|
},
|
|
getCursorScreenPoint: () => {
|
|
const jsonStr = native.symbols.getCursorScreenPoint();
|
|
if (!jsonStr) {
|
|
return { x: 0, y: 0 };
|
|
}
|
|
try {
|
|
return JSON.parse(jsonStr.toString());
|
|
} catch {
|
|
return { x: 0, y: 0 };
|
|
}
|
|
}
|
|
};
|
|
sessionCache = new Map;
|
|
Session = {
|
|
fromPartition: (partition) => {
|
|
let session = sessionCache.get(partition);
|
|
if (!session) {
|
|
session = new SessionInstance(partition);
|
|
sessionCache.set(partition, session);
|
|
}
|
|
return session;
|
|
},
|
|
get defaultSession() {
|
|
return Session.fromPartition("persist:default");
|
|
}
|
|
};
|
|
webviewDecideNavigation = new JSCallback((_webviewId, _url) => {
|
|
return true;
|
|
}, {
|
|
args: [FFIType.u32, FFIType.cstring],
|
|
returns: FFIType.u32,
|
|
threadsafe: true
|
|
});
|
|
webviewEventJSCallback = new JSCallback((id, _eventName, _detail) => {
|
|
let eventName = "";
|
|
let detail = "";
|
|
try {
|
|
eventName = new CString(_eventName).toString();
|
|
detail = new CString(_detail).toString();
|
|
} catch (err) {
|
|
console.error("[webviewEventJSCallback] Error converting strings:", err);
|
|
console.error("[webviewEventJSCallback] Raw values:", {
|
|
_eventName,
|
|
_detail
|
|
});
|
|
return;
|
|
}
|
|
webviewEventHandler(id, eventName, detail);
|
|
}, {
|
|
args: [FFIType.u32, FFIType.cstring, FFIType.cstring],
|
|
returns: FFIType.void,
|
|
threadsafe: true
|
|
});
|
|
bunBridgePostmessageHandler = new JSCallback((id, msg) => {
|
|
try {
|
|
const msgStr = new CString(msg);
|
|
if (!msgStr.length) {
|
|
return;
|
|
}
|
|
const msgJson = JSON.parse(msgStr.toString());
|
|
const webview = BrowserView.getById(id);
|
|
if (!webview)
|
|
return;
|
|
webview.rpcHandler?.(msgJson);
|
|
} catch (err) {
|
|
console.error("error sending message to bun: ", err);
|
|
console.error("msgString: ", new CString(msg));
|
|
}
|
|
}, {
|
|
args: [FFIType.u32, FFIType.cstring],
|
|
returns: FFIType.void,
|
|
threadsafe: true
|
|
});
|
|
eventBridgeHandler = new JSCallback((_id, msg) => {
|
|
try {
|
|
const message = new CString(msg);
|
|
const jsonMessage = JSON.parse(message.toString());
|
|
if (jsonMessage.id === "webviewEvent") {
|
|
const { payload } = jsonMessage;
|
|
webviewEventHandler(payload.id, payload.eventName, payload.detail);
|
|
}
|
|
} catch (err) {
|
|
console.error("error in eventBridgeHandler: ", err);
|
|
}
|
|
}, {
|
|
args: [FFIType.u32, FFIType.cstring],
|
|
returns: FFIType.void,
|
|
threadsafe: true
|
|
});
|
|
internalBridgeHandler = new JSCallback((_id, msg) => {
|
|
try {
|
|
const batchMessage = new CString(msg);
|
|
const jsonBatch = JSON.parse(batchMessage.toString());
|
|
if (jsonBatch.id === "webviewEvent") {
|
|
const { payload } = jsonBatch;
|
|
webviewEventHandler(payload.id, payload.eventName, payload.detail);
|
|
return;
|
|
}
|
|
jsonBatch.forEach((msgStr) => {
|
|
const msgJson = JSON.parse(msgStr);
|
|
if (msgJson.type === "message") {
|
|
const handler = internalRpcHandlers.message[msgJson.id];
|
|
handler?.(msgJson.payload);
|
|
} else if (msgJson.type === "request") {
|
|
const hostWebview = BrowserView.getById(msgJson.hostWebviewId);
|
|
const handler = internalRpcHandlers.request[msgJson.method];
|
|
const payload = handler?.(msgJson.params);
|
|
const resultObj = {
|
|
type: "response",
|
|
id: msgJson.id,
|
|
success: true,
|
|
payload
|
|
};
|
|
if (!hostWebview) {
|
|
console.log("--->>> internal request in bun: NO HOST WEBVIEW FOUND");
|
|
return;
|
|
}
|
|
hostWebview.sendInternalMessageViaExecute(resultObj);
|
|
}
|
|
});
|
|
} catch (err) {
|
|
console.error("error in internalBridgeHandler: ", err);
|
|
}
|
|
}, {
|
|
args: [FFIType.u32, FFIType.cstring],
|
|
returns: FFIType.void,
|
|
threadsafe: true
|
|
});
|
|
trayItemHandler = new JSCallback((id, action) => {
|
|
const actionString = (new CString(action).toString() || "").trim();
|
|
const { action: actualAction, data } = deserializeMenuAction(actionString);
|
|
const event = eventEmitter_default.events.tray.trayClicked({
|
|
id,
|
|
action: actualAction,
|
|
data
|
|
});
|
|
eventEmitter_default.emitEvent(event);
|
|
eventEmitter_default.emitEvent(event, id);
|
|
}, {
|
|
args: [FFIType.u32, FFIType.cstring],
|
|
returns: FFIType.void,
|
|
threadsafe: true
|
|
});
|
|
applicationMenuHandler = new JSCallback((id, action) => {
|
|
const actionString = new CString(action).toString();
|
|
const { action: actualAction, data } = deserializeMenuAction(actionString);
|
|
const event = eventEmitter_default.events.app.applicationMenuClicked({
|
|
id,
|
|
action: actualAction,
|
|
data
|
|
});
|
|
eventEmitter_default.emitEvent(event);
|
|
}, {
|
|
args: [FFIType.u32, FFIType.cstring],
|
|
returns: FFIType.void,
|
|
threadsafe: true
|
|
});
|
|
contextMenuHandler = new JSCallback((_id, action) => {
|
|
const actionString = new CString(action).toString();
|
|
const { action: actualAction, data } = deserializeMenuAction(actionString);
|
|
const event = eventEmitter_default.events.app.contextMenuClicked({
|
|
action: actualAction,
|
|
data
|
|
});
|
|
eventEmitter_default.emitEvent(event);
|
|
}, {
|
|
args: [FFIType.u32, FFIType.cstring],
|
|
returns: FFIType.void,
|
|
threadsafe: true
|
|
});
|
|
internalRpcHandlers = {
|
|
request: {
|
|
webviewTagInit: (params) => {
|
|
const {
|
|
hostWebviewId,
|
|
windowId,
|
|
renderer,
|
|
html,
|
|
preload,
|
|
partition,
|
|
frame,
|
|
navigationRules,
|
|
sandbox,
|
|
transparent,
|
|
passthrough
|
|
} = params;
|
|
const url = !params.url && !html ? "https://electrobun.dev" : params.url;
|
|
const webviewForTag = new BrowserView({
|
|
url,
|
|
html,
|
|
preload,
|
|
partition,
|
|
frame,
|
|
hostWebviewId,
|
|
autoResize: false,
|
|
windowId,
|
|
renderer,
|
|
navigationRules,
|
|
sandbox,
|
|
startTransparent: transparent,
|
|
startPassthrough: passthrough
|
|
});
|
|
return webviewForTag.id;
|
|
},
|
|
webviewTagCanGoBack: (params) => {
|
|
const { id } = params;
|
|
const webviewPtr = BrowserView.getById(id)?.ptr;
|
|
if (!webviewPtr) {
|
|
console.error("no webview ptr");
|
|
return false;
|
|
}
|
|
return native.symbols.webviewCanGoBack(webviewPtr);
|
|
},
|
|
webviewTagCanGoForward: (params) => {
|
|
const { id } = params;
|
|
const webviewPtr = BrowserView.getById(id)?.ptr;
|
|
if (!webviewPtr) {
|
|
console.error("no webview ptr");
|
|
return false;
|
|
}
|
|
return native.symbols.webviewCanGoForward(webviewPtr);
|
|
}
|
|
},
|
|
message: {
|
|
webviewTagResize: (params) => {
|
|
const browserView = BrowserView.getById(params.id);
|
|
const webviewPtr = browserView?.ptr;
|
|
if (!webviewPtr) {
|
|
console.log("[Bun] ERROR: webviewTagResize - no webview ptr found for id:", params.id);
|
|
return;
|
|
}
|
|
const { x, y, width, height } = params.frame;
|
|
native.symbols.resizeWebview(webviewPtr, x, y, width, height, toCString(params.masks));
|
|
},
|
|
webviewTagUpdateSrc: (params) => {
|
|
const webview = BrowserView.getById(params.id);
|
|
if (!webview || !webview.ptr) {
|
|
console.error(`webviewTagUpdateSrc: BrowserView not found or has no ptr for id ${params.id}`);
|
|
return;
|
|
}
|
|
native.symbols.loadURLInWebView(webview.ptr, toCString(params.url));
|
|
},
|
|
webviewTagUpdateHtml: (params) => {
|
|
const webview = BrowserView.getById(params.id);
|
|
if (!webview || !webview.ptr) {
|
|
console.error(`webviewTagUpdateHtml: BrowserView not found or has no ptr for id ${params.id}`);
|
|
return;
|
|
}
|
|
native.symbols.setWebviewHTMLContent(webview.id, toCString(params.html));
|
|
webview.loadHTML(params.html);
|
|
webview.html = params.html;
|
|
},
|
|
webviewTagUpdatePreload: (params) => {
|
|
const webview = BrowserView.getById(params.id);
|
|
if (!webview || !webview.ptr) {
|
|
console.error(`webviewTagUpdatePreload: BrowserView not found or has no ptr for id ${params.id}`);
|
|
return;
|
|
}
|
|
native.symbols.updatePreloadScriptToWebView(webview.ptr, toCString("electrobun_custom_preload_script"), toCString(params.preload), true);
|
|
},
|
|
webviewTagGoBack: (params) => {
|
|
const webview = BrowserView.getById(params.id);
|
|
if (!webview || !webview.ptr) {
|
|
console.error(`webviewTagGoBack: BrowserView not found or has no ptr for id ${params.id}`);
|
|
return;
|
|
}
|
|
native.symbols.webviewGoBack(webview.ptr);
|
|
},
|
|
webviewTagGoForward: (params) => {
|
|
const webview = BrowserView.getById(params.id);
|
|
if (!webview || !webview.ptr) {
|
|
console.error(`webviewTagGoForward: BrowserView not found or has no ptr for id ${params.id}`);
|
|
return;
|
|
}
|
|
native.symbols.webviewGoForward(webview.ptr);
|
|
},
|
|
webviewTagReload: (params) => {
|
|
const webview = BrowserView.getById(params.id);
|
|
if (!webview || !webview.ptr) {
|
|
console.error(`webviewTagReload: BrowserView not found or has no ptr for id ${params.id}`);
|
|
return;
|
|
}
|
|
native.symbols.webviewReload(webview.ptr);
|
|
},
|
|
webviewTagRemove: (params) => {
|
|
const webview = BrowserView.getById(params.id);
|
|
if (!webview || !webview.ptr) {
|
|
console.error(`webviewTagRemove: BrowserView not found or has no ptr for id ${params.id}`);
|
|
return;
|
|
}
|
|
native.symbols.webviewRemove(webview.ptr);
|
|
},
|
|
startWindowMove: (params) => {
|
|
const window = BrowserWindow.getById(params.id);
|
|
if (!window)
|
|
return;
|
|
native.symbols.startWindowMove(window.ptr);
|
|
},
|
|
stopWindowMove: (_params) => {
|
|
native.symbols.stopWindowMove();
|
|
},
|
|
webviewTagSetTransparent: (params) => {
|
|
const webview = BrowserView.getById(params.id);
|
|
if (!webview || !webview.ptr) {
|
|
console.error(`webviewTagSetTransparent: BrowserView not found or has no ptr for id ${params.id}`);
|
|
return;
|
|
}
|
|
native.symbols.webviewSetTransparent(webview.ptr, params.transparent);
|
|
},
|
|
webviewTagSetPassthrough: (params) => {
|
|
const webview = BrowserView.getById(params.id);
|
|
if (!webview || !webview.ptr) {
|
|
console.error(`webviewTagSetPassthrough: BrowserView not found or has no ptr for id ${params.id}`);
|
|
return;
|
|
}
|
|
native.symbols.webviewSetPassthrough(webview.ptr, params.enablePassthrough);
|
|
},
|
|
webviewTagSetHidden: (params) => {
|
|
const webview = BrowserView.getById(params.id);
|
|
if (!webview || !webview.ptr) {
|
|
console.error(`webviewTagSetHidden: BrowserView not found or has no ptr for id ${params.id}`);
|
|
return;
|
|
}
|
|
native.symbols.webviewSetHidden(webview.ptr, params.hidden);
|
|
},
|
|
webviewTagSetNavigationRules: (params) => {
|
|
const webview = BrowserView.getById(params.id);
|
|
if (!webview || !webview.ptr) {
|
|
console.error(`webviewTagSetNavigationRules: BrowserView not found or has no ptr for id ${params.id}`);
|
|
return;
|
|
}
|
|
const rulesJson = JSON.stringify(params.rules);
|
|
native.symbols.setWebviewNavigationRules(webview.ptr, toCString(rulesJson));
|
|
},
|
|
webviewTagFindInPage: (params) => {
|
|
const webview = BrowserView.getById(params.id);
|
|
if (!webview || !webview.ptr) {
|
|
console.error(`webviewTagFindInPage: BrowserView not found or has no ptr for id ${params.id}`);
|
|
return;
|
|
}
|
|
native.symbols.webviewFindInPage(webview.ptr, toCString(params.searchText), params.forward, params.matchCase);
|
|
},
|
|
webviewTagStopFind: (params) => {
|
|
const webview = BrowserView.getById(params.id);
|
|
if (!webview || !webview.ptr) {
|
|
console.error(`webviewTagStopFind: BrowserView not found or has no ptr for id ${params.id}`);
|
|
return;
|
|
}
|
|
native.symbols.webviewStopFind(webview.ptr);
|
|
},
|
|
webviewTagOpenDevTools: (params) => {
|
|
const webview = BrowserView.getById(params.id);
|
|
if (!webview || !webview.ptr) {
|
|
console.error(`webviewTagOpenDevTools: BrowserView not found or has no ptr for id ${params.id}`);
|
|
return;
|
|
}
|
|
native.symbols.webviewOpenDevTools(webview.ptr);
|
|
},
|
|
webviewTagCloseDevTools: (params) => {
|
|
const webview = BrowserView.getById(params.id);
|
|
if (!webview || !webview.ptr) {
|
|
console.error(`webviewTagCloseDevTools: BrowserView not found or has no ptr for id ${params.id}`);
|
|
return;
|
|
}
|
|
native.symbols.webviewCloseDevTools(webview.ptr);
|
|
},
|
|
webviewTagToggleDevTools: (params) => {
|
|
const webview = BrowserView.getById(params.id);
|
|
if (!webview || !webview.ptr) {
|
|
console.error(`webviewTagToggleDevTools: BrowserView not found or has no ptr for id ${params.id}`);
|
|
return;
|
|
}
|
|
native.symbols.webviewToggleDevTools(webview.ptr);
|
|
},
|
|
webviewEvent: (params) => {
|
|
console.log("-----------------+webviewEvent", params);
|
|
}
|
|
}
|
|
};
|
|
});
|
|
|
|
// node_modules/electrobun/dist/api/bun/core/BrowserWindow.ts
|
|
class BrowserWindow {
|
|
id = nextWindowId++;
|
|
ptr;
|
|
title = "Electrobun";
|
|
state = "creating";
|
|
url = null;
|
|
html = null;
|
|
preload = null;
|
|
renderer = "native";
|
|
transparent = false;
|
|
navigationRules = null;
|
|
sandbox = false;
|
|
frame = {
|
|
x: 0,
|
|
y: 0,
|
|
width: 800,
|
|
height: 600
|
|
};
|
|
webviewId;
|
|
constructor(options = defaultOptions2) {
|
|
this.title = options.title || "New Window";
|
|
this.frame = options.frame ? { ...defaultOptions2.frame, ...options.frame } : { ...defaultOptions2.frame };
|
|
this.url = options.url || null;
|
|
this.html = options.html || null;
|
|
this.preload = options.preload || null;
|
|
this.renderer = options.renderer || defaultOptions2.renderer;
|
|
this.transparent = options.transparent ?? false;
|
|
this.navigationRules = options.navigationRules || null;
|
|
this.sandbox = options.sandbox ?? false;
|
|
this.init(options);
|
|
}
|
|
init({
|
|
rpc,
|
|
styleMask,
|
|
titleBarStyle,
|
|
transparent
|
|
}) {
|
|
this.ptr = ffi.request.createWindow({
|
|
id: this.id,
|
|
title: this.title,
|
|
url: this.url || "",
|
|
frame: {
|
|
width: this.frame.width,
|
|
height: this.frame.height,
|
|
x: this.frame.x,
|
|
y: this.frame.y
|
|
},
|
|
styleMask: {
|
|
Borderless: false,
|
|
Titled: true,
|
|
Closable: true,
|
|
Miniaturizable: true,
|
|
Resizable: true,
|
|
UnifiedTitleAndToolbar: false,
|
|
FullScreen: false,
|
|
FullSizeContentView: false,
|
|
UtilityWindow: false,
|
|
DocModalWindow: false,
|
|
NonactivatingPanel: false,
|
|
HUDWindow: false,
|
|
...styleMask || {},
|
|
...titleBarStyle === "hiddenInset" ? {
|
|
Titled: true,
|
|
FullSizeContentView: true
|
|
} : {},
|
|
...titleBarStyle === "hidden" ? {
|
|
Titled: false,
|
|
FullSizeContentView: true
|
|
} : {}
|
|
},
|
|
titleBarStyle: titleBarStyle || "default",
|
|
transparent: transparent ?? false
|
|
});
|
|
BrowserWindowMap[this.id] = this;
|
|
const webview = new BrowserView({
|
|
url: this.url,
|
|
html: this.html,
|
|
preload: this.preload,
|
|
renderer: this.renderer,
|
|
frame: {
|
|
x: 0,
|
|
y: 0,
|
|
width: this.frame.width,
|
|
height: this.frame.height
|
|
},
|
|
rpc,
|
|
windowId: this.id,
|
|
navigationRules: this.navigationRules,
|
|
sandbox: this.sandbox
|
|
});
|
|
console.log("setting webviewId: ", webview.id);
|
|
this.webviewId = webview.id;
|
|
}
|
|
get webview() {
|
|
return BrowserView.getById(this.webviewId);
|
|
}
|
|
static getById(id) {
|
|
return BrowserWindowMap[id];
|
|
}
|
|
setTitle(title) {
|
|
this.title = title;
|
|
return ffi.request.setTitle({ winId: this.id, title });
|
|
}
|
|
close() {
|
|
return ffi.request.closeWindow({ winId: this.id });
|
|
}
|
|
focus() {
|
|
return ffi.request.focusWindow({ winId: this.id });
|
|
}
|
|
show() {
|
|
return ffi.request.focusWindow({ winId: this.id });
|
|
}
|
|
minimize() {
|
|
return ffi.request.minimizeWindow({ winId: this.id });
|
|
}
|
|
unminimize() {
|
|
return ffi.request.restoreWindow({ winId: this.id });
|
|
}
|
|
isMinimized() {
|
|
return ffi.request.isWindowMinimized({ winId: this.id });
|
|
}
|
|
maximize() {
|
|
return ffi.request.maximizeWindow({ winId: this.id });
|
|
}
|
|
unmaximize() {
|
|
return ffi.request.unmaximizeWindow({ winId: this.id });
|
|
}
|
|
isMaximized() {
|
|
return ffi.request.isWindowMaximized({ winId: this.id });
|
|
}
|
|
setFullScreen(fullScreen) {
|
|
return ffi.request.setWindowFullScreen({ winId: this.id, fullScreen });
|
|
}
|
|
isFullScreen() {
|
|
return ffi.request.isWindowFullScreen({ winId: this.id });
|
|
}
|
|
setAlwaysOnTop(alwaysOnTop) {
|
|
return ffi.request.setWindowAlwaysOnTop({ winId: this.id, alwaysOnTop });
|
|
}
|
|
isAlwaysOnTop() {
|
|
return ffi.request.isWindowAlwaysOnTop({ winId: this.id });
|
|
}
|
|
setPosition(x, y) {
|
|
this.frame.x = x;
|
|
this.frame.y = y;
|
|
return ffi.request.setWindowPosition({ winId: this.id, x, y });
|
|
}
|
|
setSize(width, height) {
|
|
this.frame.width = width;
|
|
this.frame.height = height;
|
|
return ffi.request.setWindowSize({ winId: this.id, width, height });
|
|
}
|
|
setFrame(x, y, width, height) {
|
|
this.frame = { x, y, width, height };
|
|
return ffi.request.setWindowFrame({ winId: this.id, x, y, width, height });
|
|
}
|
|
getFrame() {
|
|
const frame = ffi.request.getWindowFrame({ winId: this.id });
|
|
this.frame = frame;
|
|
return frame;
|
|
}
|
|
getPosition() {
|
|
const frame = this.getFrame();
|
|
return { x: frame.x, y: frame.y };
|
|
}
|
|
getSize() {
|
|
const frame = this.getFrame();
|
|
return { width: frame.width, height: frame.height };
|
|
}
|
|
on(name, handler) {
|
|
const specificName = `${name}-${this.id}`;
|
|
eventEmitter_default.on(specificName, handler);
|
|
}
|
|
}
|
|
var buildConfig3, nextWindowId = 1, defaultOptions2, BrowserWindowMap;
|
|
var init_BrowserWindow = __esm(async () => {
|
|
init_eventEmitter();
|
|
init_BuildConfig();
|
|
await __promiseAll([
|
|
init_native(),
|
|
init_BrowserView(),
|
|
init_Utils()
|
|
]);
|
|
buildConfig3 = await BuildConfig.get();
|
|
defaultOptions2 = {
|
|
title: "Electrobun",
|
|
frame: {
|
|
x: 0,
|
|
y: 0,
|
|
width: 800,
|
|
height: 600
|
|
},
|
|
url: "https://electrobun.dev",
|
|
html: null,
|
|
preload: null,
|
|
renderer: buildConfig3.defaultRenderer,
|
|
titleBarStyle: "default",
|
|
transparent: false,
|
|
navigationRules: null,
|
|
sandbox: false
|
|
};
|
|
BrowserWindowMap = {};
|
|
eventEmitter_default.on("close", (event) => {
|
|
const windowId = event.data.id;
|
|
delete BrowserWindowMap[windowId];
|
|
for (const view of BrowserView.getAll()) {
|
|
if (view.windowId === windowId) {
|
|
view.remove();
|
|
}
|
|
}
|
|
const exitOnLastWindowClosed = buildConfig3.runtime?.exitOnLastWindowClosed ?? true;
|
|
if (exitOnLastWindowClosed && Object.keys(BrowserWindowMap).length === 0) {
|
|
quit();
|
|
}
|
|
});
|
|
});
|
|
|
|
// node_modules/electrobun/dist/api/bun/index.ts
|
|
init_eventEmitter();
|
|
await __promiseAll([
|
|
init_BrowserWindow(),
|
|
init_BrowserView(),
|
|
init_Tray()
|
|
]);
|
|
|
|
// node_modules/electrobun/dist/api/bun/core/ApplicationMenu.ts
|
|
init_eventEmitter();
|
|
await init_native();
|
|
var exports_ApplicationMenu = {};
|
|
__export(exports_ApplicationMenu, {
|
|
setApplicationMenu: () => setApplicationMenu,
|
|
on: () => on
|
|
});
|
|
|
|
// node_modules/electrobun/dist/api/bun/core/menuRoles.ts
|
|
var roleLabelMap = {
|
|
about: "About",
|
|
quit: "Quit",
|
|
hide: "Hide",
|
|
hideOthers: "Hide Others",
|
|
showAll: "Show All",
|
|
minimize: "Minimize",
|
|
zoom: "Zoom",
|
|
close: "Close",
|
|
bringAllToFront: "Bring All To Front",
|
|
cycleThroughWindows: "Cycle Through Windows",
|
|
enterFullScreen: "Enter Full Screen",
|
|
exitFullScreen: "Exit Full Screen",
|
|
toggleFullScreen: "Toggle Full Screen",
|
|
undo: "Undo",
|
|
redo: "Redo",
|
|
cut: "Cut",
|
|
copy: "Copy",
|
|
paste: "Paste",
|
|
pasteAndMatchStyle: "Paste and Match Style",
|
|
delete: "Delete",
|
|
selectAll: "Select All",
|
|
startSpeaking: "Start Speaking",
|
|
stopSpeaking: "Stop Speaking",
|
|
showHelp: "Show Help",
|
|
moveForward: "Move Forward",
|
|
moveBackward: "Move Backward",
|
|
moveLeft: "Move Left",
|
|
moveRight: "Move Right",
|
|
moveUp: "Move Up",
|
|
moveDown: "Move Down",
|
|
moveWordForward: "Move Word Forward",
|
|
moveWordBackward: "Move Word Backward",
|
|
moveWordLeft: "Move Word Left",
|
|
moveWordRight: "Move Word Right",
|
|
moveToBeginningOfLine: "Move to Beginning of Line",
|
|
moveToEndOfLine: "Move to End of Line",
|
|
moveToLeftEndOfLine: "Move to Left End of Line",
|
|
moveToRightEndOfLine: "Move to Right End of Line",
|
|
moveToBeginningOfParagraph: "Move to Beginning of Paragraph",
|
|
moveToEndOfParagraph: "Move to End of Paragraph",
|
|
moveParagraphForward: "Move Paragraph Forward",
|
|
moveParagraphBackward: "Move Paragraph Backward",
|
|
moveToBeginningOfDocument: "Move to Beginning of Document",
|
|
moveToEndOfDocument: "Move to End of Document",
|
|
moveForwardAndModifySelection: "Move Forward and Modify Selection",
|
|
moveBackwardAndModifySelection: "Move Backward and Modify Selection",
|
|
moveLeftAndModifySelection: "Move Left and Modify Selection",
|
|
moveRightAndModifySelection: "Move Right and Modify Selection",
|
|
moveUpAndModifySelection: "Move Up and Modify Selection",
|
|
moveDownAndModifySelection: "Move Down and Modify Selection",
|
|
moveWordForwardAndModifySelection: "Move Word Forward and Modify Selection",
|
|
moveWordBackwardAndModifySelection: "Move Word Backward and Modify Selection",
|
|
moveWordLeftAndModifySelection: "Move Word Left and Modify Selection",
|
|
moveWordRightAndModifySelection: "Move Word Right and Modify Selection",
|
|
moveToBeginningOfLineAndModifySelection: "Move to Beginning of Line and Modify Selection",
|
|
moveToEndOfLineAndModifySelection: "Move to End of Line and Modify Selection",
|
|
moveToLeftEndOfLineAndModifySelection: "Move to Left End of Line and Modify Selection",
|
|
moveToRightEndOfLineAndModifySelection: "Move to Right End of Line and Modify Selection",
|
|
moveToBeginningOfParagraphAndModifySelection: "Move to Beginning of Paragraph and Modify Selection",
|
|
moveToEndOfParagraphAndModifySelection: "Move to End of Paragraph and Modify Selection",
|
|
moveParagraphForwardAndModifySelection: "Move Paragraph Forward and Modify Selection",
|
|
moveParagraphBackwardAndModifySelection: "Move Paragraph Backward and Modify Selection",
|
|
moveToBeginningOfDocumentAndModifySelection: "Move to Beginning of Document and Modify Selection",
|
|
moveToEndOfDocumentAndModifySelection: "Move to End of Document and Modify Selection",
|
|
pageUp: "Page Up",
|
|
pageDown: "Page Down",
|
|
pageUpAndModifySelection: "Page Up and Modify Selection",
|
|
pageDownAndModifySelection: "Page Down and Modify Selection",
|
|
scrollLineUp: "Scroll Line Up",
|
|
scrollLineDown: "Scroll Line Down",
|
|
scrollPageUp: "Scroll Page Up",
|
|
scrollPageDown: "Scroll Page Down",
|
|
scrollToBeginningOfDocument: "Scroll to Beginning of Document",
|
|
scrollToEndOfDocument: "Scroll to End of Document",
|
|
centerSelectionInVisibleArea: "Center Selection in Visible Area",
|
|
deleteBackward: "Delete Backward",
|
|
deleteForward: "Delete Forward",
|
|
deleteBackwardByDecomposingPreviousCharacter: "Delete Backward by Decomposing Previous Character",
|
|
deleteWordBackward: "Delete Word Backward",
|
|
deleteWordForward: "Delete Word Forward",
|
|
deleteToBeginningOfLine: "Delete to Beginning of Line",
|
|
deleteToEndOfLine: "Delete to End of Line",
|
|
deleteToBeginningOfParagraph: "Delete to Beginning of Paragraph",
|
|
deleteToEndOfParagraph: "Delete to End of Paragraph",
|
|
selectWord: "Select Word",
|
|
selectLine: "Select Line",
|
|
selectParagraph: "Select Paragraph",
|
|
selectToMark: "Select to Mark",
|
|
setMark: "Set Mark",
|
|
swapWithMark: "Swap with Mark",
|
|
deleteToMark: "Delete to Mark",
|
|
capitalizeWord: "Capitalize Word",
|
|
uppercaseWord: "Uppercase Word",
|
|
lowercaseWord: "Lowercase Word",
|
|
transpose: "Transpose",
|
|
transposeWords: "Transpose Words",
|
|
insertNewline: "Insert Newline",
|
|
insertLineBreak: "Insert Line Break",
|
|
insertParagraphSeparator: "Insert Paragraph Separator",
|
|
insertTab: "Insert Tab",
|
|
insertBacktab: "Insert Backtab",
|
|
insertTabIgnoringFieldEditor: "Insert Tab Ignoring Field Editor",
|
|
insertNewlineIgnoringFieldEditor: "Insert Newline Ignoring Field Editor",
|
|
yank: "Yank",
|
|
yankAndSelect: "Yank and Select",
|
|
complete: "Complete",
|
|
cancelOperation: "Cancel Operation",
|
|
indent: "Indent"
|
|
};
|
|
|
|
// node_modules/electrobun/dist/api/bun/core/ApplicationMenu.ts
|
|
var setApplicationMenu = (menu) => {
|
|
const menuWithDefaults = menuConfigWithDefaults2(menu);
|
|
ffi.request.setApplicationMenu({
|
|
menuConfig: JSON.stringify(menuWithDefaults)
|
|
});
|
|
};
|
|
var on = (name, handler) => {
|
|
const specificName = `${name}`;
|
|
eventEmitter_default.on(specificName, handler);
|
|
};
|
|
var menuConfigWithDefaults2 = (menu) => {
|
|
return menu.map((item) => {
|
|
if (item.type === "divider" || item.type === "separator") {
|
|
return { type: "divider" };
|
|
} else {
|
|
const menuItem = item;
|
|
const actionWithDataId = ffi.internal.serializeMenuAction(menuItem.action || "", menuItem.data);
|
|
return {
|
|
label: menuItem.label || roleLabelMap[menuItem.role] || "",
|
|
type: menuItem.type || "normal",
|
|
...menuItem.role ? { role: menuItem.role } : { action: actionWithDataId },
|
|
enabled: menuItem.enabled === false ? false : true,
|
|
checked: Boolean(menuItem.checked),
|
|
hidden: Boolean(menuItem.hidden),
|
|
tooltip: menuItem.tooltip || undefined,
|
|
accelerator: menuItem.accelerator || undefined,
|
|
...menuItem.submenu ? { submenu: menuConfigWithDefaults2(menuItem.submenu) } : {}
|
|
};
|
|
}
|
|
});
|
|
};
|
|
|
|
// node_modules/electrobun/dist/api/bun/core/ContextMenu.ts
|
|
init_eventEmitter();
|
|
await init_native();
|
|
var exports_ContextMenu = {};
|
|
__export(exports_ContextMenu, {
|
|
showContextMenu: () => showContextMenu,
|
|
on: () => on2
|
|
});
|
|
var showContextMenu = (menu) => {
|
|
const menuWithDefaults = menuConfigWithDefaults3(menu);
|
|
ffi.request.showContextMenu({
|
|
menuConfig: JSON.stringify(menuWithDefaults)
|
|
});
|
|
};
|
|
var on2 = (name, handler) => {
|
|
const specificName = `${name}`;
|
|
eventEmitter_default.on(specificName, handler);
|
|
};
|
|
var menuConfigWithDefaults3 = (menu) => {
|
|
return menu.map((item) => {
|
|
if (item.type === "divider" || item.type === "separator") {
|
|
return { type: "divider" };
|
|
} else {
|
|
const menuItem = item;
|
|
const actionWithDataId = ffi.internal.serializeMenuAction(menuItem.action || "", menuItem.data);
|
|
return {
|
|
label: menuItem.label || roleLabelMap[menuItem.role] || "",
|
|
type: menuItem.type || "normal",
|
|
...menuItem.role ? { role: menuItem.role } : { action: actionWithDataId },
|
|
enabled: menuItem.enabled === false ? false : true,
|
|
checked: Boolean(menuItem.checked),
|
|
hidden: Boolean(menuItem.hidden),
|
|
tooltip: menuItem.tooltip || undefined,
|
|
...menuItem.accelerator ? { accelerator: menuItem.accelerator } : {},
|
|
...menuItem.submenu ? { submenu: menuConfigWithDefaults3(menuItem.submenu) } : {}
|
|
};
|
|
}
|
|
});
|
|
};
|
|
|
|
// node_modules/electrobun/dist/api/bun/index.ts
|
|
init_Paths();
|
|
init_BuildConfig();
|
|
await __promiseAll([
|
|
init_Updater(),
|
|
init_Utils(),
|
|
init_Socket(),
|
|
init_native()
|
|
]);
|
|
var Electrobun = {
|
|
BrowserWindow,
|
|
BrowserView,
|
|
Tray,
|
|
Updater,
|
|
Utils: exports_Utils,
|
|
ApplicationMenu: exports_ApplicationMenu,
|
|
ContextMenu: exports_ContextMenu,
|
|
GlobalShortcut,
|
|
Screen,
|
|
Session,
|
|
BuildConfig,
|
|
events: eventEmitter_default,
|
|
PATHS: exports_Paths,
|
|
Socket: exports_Socket
|
|
};
|
|
var bun_default = Electrobun;
|
|
|
|
// src/bun/index.ts
|
|
import { join as join5 } from "path";
|
|
|
|
// src/bun/mainWindow.ts
|
|
var win = null;
|
|
function getAppUrl() {
|
|
if (process.env.ELECTROBUN_START_URL || process.env.ELECTRON_START_URL) {
|
|
return process.env.ELECTROBUN_START_URL || process.env.ELECTRON_START_URL || "http://localhost:5173";
|
|
}
|
|
return "views://main/index.html";
|
|
}
|
|
function setupWindowEvents(browserWindow, rpc) {
|
|
if (!browserWindow)
|
|
return;
|
|
browserWindow.on?.("resize", () => {
|
|
const webview = browserWindow.webview;
|
|
if (webview?.rpc) {
|
|
try {
|
|
webview.rpc.windowState?.({
|
|
isMaximized: browserWindow.isMaximized?.(),
|
|
isFullScreen: browserWindow.isFullScreen?.()
|
|
});
|
|
} catch (_) {}
|
|
}
|
|
});
|
|
const sendWindowState = (state) => {
|
|
const webview = browserWindow.webview;
|
|
if (webview?.rpc) {
|
|
try {
|
|
webview.rpc.windowState?.(state);
|
|
} catch (_) {}
|
|
}
|
|
};
|
|
browserWindow.on?.("maximize", () => sendWindowState({ isMaximized: true }));
|
|
browserWindow.on?.("unmaximize", () => sendWindowState({ isMaximized: false }));
|
|
browserWindow.on?.("enter-full-screen", () => sendWindowState({ isFullScreen: true }));
|
|
browserWindow.on?.("leave-full-screen", () => sendWindowState({ isFullScreen: false }));
|
|
}
|
|
function createMainWindow(rpc) {
|
|
const url = getAppUrl();
|
|
win = new BrowserWindow({
|
|
title: "Farm Control",
|
|
url,
|
|
frame: {
|
|
width: 1200,
|
|
height: 800
|
|
},
|
|
titleBarStyle: "hiddenInset",
|
|
preload: "views://preload/index.js",
|
|
rpc,
|
|
styleMask: {
|
|
Titled: true,
|
|
Closable: true,
|
|
Miniaturizable: true,
|
|
Resizable: true,
|
|
FullSizeContentView: true
|
|
}
|
|
});
|
|
setupWindowEvents(win, rpc);
|
|
bun_default.events.on("close", () => {
|
|
const wins = BrowserWindow.getAll?.() ?? [];
|
|
if (wins.length <= 1) {
|
|
if (process.platform !== "darwin") {
|
|
exports_Utils.quit?.();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
function getMainWindow() {
|
|
return win;
|
|
}
|
|
|
|
// src/bun/spotlightWindow.ts
|
|
var spotlightWin = null;
|
|
function getSpotlightRouteUrl() {
|
|
const routePath = "/dashboard/electron/spotlightcontent";
|
|
if (process.env.ELECTROBUN_START_URL || process.env.ELECTRON_START_URL) {
|
|
const base = String(process.env.ELECTROBUN_START_URL || process.env.ELECTRON_START_URL).replace(/\/$/, "");
|
|
return `${base}${routePath}`;
|
|
}
|
|
return `views://main/index.html#${routePath}`;
|
|
}
|
|
function openSpotlightContentWindow() {
|
|
if (spotlightWin && !spotlightWin.isDestroyed?.()) {
|
|
spotlightWin.show?.();
|
|
spotlightWin.focus?.();
|
|
return;
|
|
}
|
|
const target = getSpotlightRouteUrl();
|
|
spotlightWin = new BrowserWindow({
|
|
title: "Spotlight",
|
|
url: target,
|
|
frame: {
|
|
width: 700,
|
|
height: 40
|
|
},
|
|
titleBarStyle: "hidden",
|
|
transparent: true,
|
|
resizable: false
|
|
});
|
|
spotlightWin.on?.("close", (e) => {
|
|
if (e?.preventDefault)
|
|
e.preventDefault();
|
|
if (spotlightWin && !spotlightWin.isDestroyed?.()) {
|
|
spotlightWin.hide?.();
|
|
}
|
|
});
|
|
spotlightWin.on?.("blur", () => {
|
|
if (spotlightWin && !spotlightWin.isDestroyed?.()) {
|
|
spotlightWin.hide?.();
|
|
}
|
|
});
|
|
}
|
|
function registerGlobalShortcuts() {
|
|
try {
|
|
const success = GlobalShortcut.register("Alt+Shift+Q", () => {
|
|
openSpotlightContentWindow();
|
|
});
|
|
if (!success) {
|
|
console.warn("[GlobalShortcut] Failed to register Alt+Shift+Q");
|
|
}
|
|
} catch (e) {
|
|
console.warn("[GlobalShortcut] Error:", e?.message);
|
|
}
|
|
}
|
|
function setupSpotlightRPC(height) {
|
|
if (!spotlightWin || spotlightWin.isDestroyed?.())
|
|
return false;
|
|
try {
|
|
const frame = spotlightWin.getFrame?.();
|
|
if (frame) {
|
|
spotlightWin.setSize?.(frame.width, height);
|
|
spotlightWin.center?.();
|
|
}
|
|
return true;
|
|
} catch (e) {
|
|
console.warn("[spotlight] Failed to resize:", e?.message);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// src/bun/index.ts
|
|
var AUTH_FILE = join5(exports_Utils.paths.userData, "auth-session.json");
|
|
async function readAuthSession() {
|
|
try {
|
|
const f = Bun.file(AUTH_FILE);
|
|
if (!await f.exists())
|
|
return null;
|
|
const raw = await f.text();
|
|
return JSON.parse(raw);
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
async function writeAuthSession(session) {
|
|
try {
|
|
await Bun.write(AUTH_FILE, JSON.stringify(session));
|
|
return true;
|
|
} catch (e) {
|
|
console.warn("[auth] Failed to write session:", e);
|
|
return false;
|
|
}
|
|
}
|
|
async function clearAuthSession() {
|
|
try {
|
|
const f = Bun.file(AUTH_FILE);
|
|
if (await f.exists()) {
|
|
await Bun.write(AUTH_FILE, "{}");
|
|
}
|
|
return true;
|
|
} catch (e) {
|
|
console.warn("[auth] Failed to clear session:", e);
|
|
return false;
|
|
}
|
|
}
|
|
var farmControlRPC = BrowserView.defineRPC({
|
|
maxRequestTime: 5000,
|
|
handlers: {
|
|
requests: {
|
|
osInfo: () => ({ platform: process.platform }),
|
|
windowState: () => {
|
|
const win2 = getMainWindow();
|
|
if (!win2 || win2.isDestroyed?.()) {
|
|
return { isFullScreen: false, isMaximized: false };
|
|
}
|
|
return {
|
|
isFullScreen: win2.isFullScreen?.(),
|
|
isMaximized: win2.isMaximized?.()
|
|
};
|
|
},
|
|
openExternalUrl: ({ url }) => {
|
|
exports_Utils.openExternal(url);
|
|
},
|
|
openInternalUrl: ({ url }) => {
|
|
const win2 = getMainWindow();
|
|
if (!win2 || win2.isDestroyed?.()) {
|
|
createMainWindow(farmControlRPC);
|
|
const newWin = getMainWindow();
|
|
if (newWin?.webview) {
|
|
newWin.webview.on?.("dom-ready", () => {
|
|
newWin.webview.rpc?.navigate?.({ url });
|
|
newWin.show?.();
|
|
newWin.focus?.();
|
|
});
|
|
}
|
|
} else if (win2.webview) {
|
|
win2.webview.rpc?.navigate?.({ url });
|
|
win2.show?.();
|
|
win2.focus?.();
|
|
}
|
|
return true;
|
|
},
|
|
windowControl: ({ action }) => {
|
|
const win2 = getMainWindow();
|
|
if (!win2)
|
|
return;
|
|
switch (action) {
|
|
case "minimize":
|
|
win2.minimize?.();
|
|
break;
|
|
case "maximize":
|
|
if (win2.isMaximized?.()) {
|
|
win2.unmaximize?.();
|
|
} else {
|
|
win2.maximize?.();
|
|
}
|
|
break;
|
|
case "close":
|
|
win2.close?.();
|
|
break;
|
|
}
|
|
},
|
|
authSessionGet: () => readAuthSession(),
|
|
authSessionSet: ({ session }) => writeAuthSession(session),
|
|
authSessionClear: () => clearAuthSession(),
|
|
spotlightWindowResize: ({ height }) => setupSpotlightRPC(height)
|
|
},
|
|
messages: {}
|
|
}
|
|
});
|
|
createMainWindow(farmControlRPC);
|
|
registerGlobalShortcuts();
|
|
var env = "development".trim();
|
|
if (env === "development") {
|
|
exports_ApplicationMenu.setApplicationMenu([
|
|
{
|
|
label: "Developer",
|
|
submenu: [
|
|
{
|
|
label: "Toggle Developer Tools",
|
|
accelerator: process.platform === "darwin" ? "Alt+Command+I" : "Ctrl+Shift+I",
|
|
action: "toggle-devtools"
|
|
}
|
|
]
|
|
}
|
|
]);
|
|
} else {
|
|
exports_ApplicationMenu.setApplicationMenu([]);
|
|
}
|
|
bun_default.events.on("open-url", (e) => {
|
|
const url = e.data.url;
|
|
if (url.startsWith("farmcontrol://app")) {
|
|
const redirectPath = url.replace("farmcontrol://app", "") || "/";
|
|
const win2 = getMainWindow();
|
|
if (win2?.webview) {
|
|
win2.webview.rpc?.navigate?.({ url: redirectPath });
|
|
}
|
|
}
|
|
});
|
|
bun_default.events.on("before-quit", () => {
|
|
GlobalShortcut.unregisterAll();
|
|
});
|