mixone
Version:
MixOne is a Node scaffolding tool implemented based on Vite, used for compiling HTML5, JavasCript, Vue, React and other codes. It supports packaging Web applications with multiple HTML entry points (BS architecture) and desktop installation packages (CS a
789 lines (769 loc) • 34.4 kB
JavaScript
// preload.js
const { contextBridge, ipcRenderer } = require('electron');
const windowController = require('./window-controller');
// 监听主进程日志
ipcRenderer.on('main-process-log', (event, { type, args }) => {
try {
// 确保 type 是有效的控制台方法
const validTypes = ['log', 'error', 'warn', 'info', 'debug'];
type = validTypes.includes(type) ? type : 'log';
// 确保 args 是数组且可以安全序列化
args = Array.isArray(args) ? args.map(arg => {
if (typeof arg === 'object' && arg !== null) {
try {
return JSON.parse(JSON.stringify(arg));
} catch (e) {
return String(arg);
}
}
return arg;
}) : [String(args)];
console[type](...args);
} catch (error) {
console.error('处理主进程日志失败:', error);
}
});
const fromWinId = Number(process.argv.find(arg => arg.startsWith('--REFERER-window-id')).split('=')[1]);
const _winId = Number(process.argv.find(arg => arg.startsWith('--window-id=')).split('=')[1]);
windowController.setWinId(_winId);
// 监听指定窗口的事件
ipcRenderer.on(`windowEvent:${_winId}`, (event, { eventName, data }) => {
const listeners = windowEventListeners.get(eventName);
if (listeners) {
listeners.forEach(listener => listener(data));
}
});
// 监听广播事件
ipcRenderer.on('broadcast', (event, { channel, data }) => {
const listeners = windowEventListeners.get(channel);
if (listeners) {
listeners.forEach(listener => listener(data));
}
});
contextBridge.exposeInMainWorld('winId', _winId);
contextBridge.exposeInMainWorld('fromWinId', fromWinId);
// 存储事件监听器
const windowEventListeners = new Map();
const windowObjEventListeners = new Map();
// 生成事件键
function generateEventKey(eventName, winId) {
return `${eventName}___${winId}`;
}
// 监听从主进程转发来的窗口事件
ipcRenderer.on('broadcast-window-event', (event, { eventName, args, winId }) => {
// 只处理属于当前窗口的事件
const eventKey = generateEventKey(eventName, winId);
const listeners = windowEventListeners.get(eventKey);
if (listeners) {
listeners.forEach(listener => listener(...args));
}
});
// 监听从主进程转发来的窗口事件
ipcRenderer.on('broadcast-window-obj-event', (event, { eventName, args, winId }) => {
// 只处理属于当前窗口的事件
const eventKey = generateEventKey(eventName, winId);
const listeners = windowObjEventListeners.get(eventKey);
if (listeners) {
listeners.forEach(listener => listener(...args));
}
});
// 跟踪已注册的事件监听器
const registeredCallbacks = new Map();
// 暴露安全的 API 给渲染进程
contextBridge.exposeInMainWorld('electron', {
// 调用主进程函数的方法
callMainFunction: async (id, params) => {
try {
const response = await ipcRenderer.invoke('call-main-fn', { id, args: params });
if (!response.success) {
throw new Error(response.error);
}
return response.result;
} catch (error) {
throw error;
}
},
// 注册回调函数的方法
registerCallback: (callbackId, callback) => {
// 检查是否已经注册过这个回调
if (registeredCallbacks.has(callbackId)) {
// 如果已注册,先移除旧的监听器
const oldListener = registeredCallbacks.get(callbackId);
ipcRenderer.removeListener('callback:' + callbackId, oldListener);
}
// 创建新的监听器函数
const listener = (event, ...args) => {
callback(...args);
};
// 注册新的监听器
ipcRenderer.on('callback:' + callbackId, listener);
// 保存监听器引用
registeredCallbacks.set(callbackId, listener);
}
});
function resolveRelativePath(basePath, relativePath) {
// 1. 分离hash部分
const [baseWithoutHash] = basePath.split('#');
// 2. 智能判断是否需要移除"文件名"
let baseDir = baseWithoutHash;
const lastSlashIndex = baseWithoutHash.lastIndexOf('/');
const lastPart = baseWithoutHash.substring(lastSlashIndex + 1);
// 判断条件:
// 1. 不以/结尾 且
// 2. 最后部分包含.扩展名
const shouldRemoveFile = lastSlashIndex !== baseWithoutHash.length - 1 &&
lastPart.includes('.');
if (shouldRemoveFile) {
baseDir = baseWithoutHash.substring(0, lastSlashIndex + 1); // 保留末尾的/
}
// 3. 处理相对路径
const baseParts = baseDir.replace('file:///', '').replace('http://', '').replace('https://', '').split('/').filter(part => part !== '');
const relativeParts = relativePath.split('/').filter(part => part !== '' && part !== '.');
// 处理 ../
for (const part of relativeParts) {
if (part === '..') {
if (baseParts.length > 0) baseParts.pop();
} else {
baseParts.push(part);
}
}
// 4. 重新构建路径
let resolvedPath = baseParts.join('/');
// 5. 恢复协议
if (basePath.startsWith('file:///')) {
resolvedPath = 'file:///' + resolvedPath;
} else if (basePath.includes('://')) {
const [protocol] = basePath.split('://');
resolvedPath = protocol + '://' + resolvedPath;
} else if (!resolvedPath.startsWith('/')) {
resolvedPath = '/' + resolvedPath;
}
return resolvedPath;
}
const windowManager = {
openWindow: async (...args) => {
if (args[1]) {
args[1]['fromWinId'] = _winId
} else {
args[1] = {}
args[1]['fromWinId'] = _winId
}
let response = await ipcRenderer.invoke('window-manager-action', {
method: '_openWindow',
args
});
const windowProperties = [
// 'webContents',
'id', 'tabbingIdentifier', 'autoHideMenuBar', 'simpleFullScreen', 'fullScreen',
'focusable',
'visibleOnAllWorkspaces', 'shadow',
'menuBarVisible', 'kiosk',
'documentEdited', 'representedFilename', 'title', 'minimizable',
'maximizable', 'fullScreenable', 'resizable', 'closable', 'movable',
'excludedFromShownWindowsMenu', 'accessibleTitle'
];
const windowMethods = ["destroy", "close", "focus", "blur", "isFocused", "isDestroyed", "show", "showInactive", "hide", "isVisible", "isModal", "maximize", "unmaximize",
"isMaximized", "minimize", "restore", "isMinimized", "setFullScreen", "isFullScreen", "setSimpleFullScreen", "isSimpleFullScreen", "isNormal", "setAspectRatio",
"setBackgroundColor", "previewFile", "closeFilePreview", "setBounds", "getBounds", "getBackgroundColor", "setContentBounds", "getContentBounds", "getNormalBounds",
"setEnabled", "isEnabled", "setSize", "getSize", "setContentSize", "getContentSize", "setMinimumSize", "getMinimumSize", "setMaximumSize", "getMaximumSize",
"setResizable", "isResizable", "setMovable", "isMovable", "setMinimizable", "isMinimizable", "setMaximizable", "isMaximizable", "setFullScreenable",
"isFullScreenable", "setClosable", "isClosable", "setHiddenInMissionControl", "isHiddenInMissionControl", "setAlwaysOnTop", "isAlwaysOnTop", "moveAbove", "moveTop",
"center", "setPosition", "getPosition", "setTitle", "getTitle", "setSheetOffset", "flashFrame", "setSkipTaskbar", "setKiosk", "isKiosk", "isTabletMode",
"getMediaSourceId", "getNativeWindowHandle", "hookWindowMessage", "isWindowMessageHooked", "unhookWindowMessage", "unhookAllWindowMessages",
"setRepresentedFilename", "getRepresentedFilename", "setDocumentEdited", "isDocumentEdited", "focusOnWebView", "blurWebView", "capturePage", "loadURL", "loadFile",
"reload", "setMenu", "removeMenu", "setProgressBar", "setOverlayIcon", "invalidateShadow", "setHasShadow", "hasShadow", "setOpacity", "getOpacity", "setShape",
"setThumbarButtons", "setThumbnailClip", "setThumbnailToolTip", "setAppDetails", "showDefinitionForSelection", "setIcon", "setWindowButtonVisibility",
"setAutoHideMenuBar", "isMenuBarAutoHide", "setMenuBarVisibility", "isMenuBarVisible", "setVisibleOnAllWorkspaces", "isVisibleOnAllWorkspaces",
"setIgnoreMouseEvents", "setContentProtection", "setFocusable", "isFocusable", "setParentWindow", "getParentWindow", "getChildWindows", "setAutoHideCursor",
"selectPreviousTab", "selectNextTab", "showAllTabs", "mergeAllWindows", "moveTabToNewWindow", "toggleTabBar", "addTabbedWindow", "setVibrancy",
"setBackgroundMaterial", "setWindowButtonPosition", "getWindowButtonPosition", "setTouchBar", "setBrowserView", "getBrowserView", "addBrowserView",
"removeBrowserView", "setTopBrowserView", "getBrowserViews", "setTitleBarOverlay"]
if (response.success) {
let winId = response.result.winId;
const obj = {
...response.result,
webContents: {
// 添加窗口事件监听方法
on: (eventName, callback) => {
const eventKey = generateEventKey(eventName, winId);
if (!windowEventListeners.has(eventKey)) {
windowEventListeners.set(eventKey, new Set());
}
windowEventListeners.get(eventKey).add(callback);
},
// 移除窗口事件监听方法
off: (eventName, callback) => {
const eventKey = generateEventKey(eventName, winId);
const listeners = windowEventListeners.get(eventKey);
if (listeners) {
listeners.delete(callback);
if (listeners.size === 0) {
windowEventListeners.delete(eventKey);
}
}
},
// 一次性事件监听
once: (eventName, callback) => {
const eventKey = generateEventKey(eventName, winId);
const onceCallback = (...args) => {
callback(...args);
(windowEventListeners.get(eventKey) || {delete:()=>{}}).delete(onceCallback);
};
if (!windowEventListeners.has(eventKey)) {
windowEventListeners.set(eventKey, new Set());
}
windowEventListeners.get(eventKey).add(onceCallback);
},
},
// 添加窗口对象的事件处理方法
on: (eventName, callback) => {
const eventKey = generateEventKey(eventName, winId);
if (!windowObjEventListeners.has(eventKey)) {
windowObjEventListeners.set(eventKey, new Set());
}
windowObjEventListeners.get(eventKey).add(callback);
},
off: (eventName, callback) => {
const eventKey = generateEventKey(eventName, winId);
const listeners = windowObjEventListeners.get(eventKey);
if (listeners) {
listeners.delete(callback);
if (listeners.size === 0) {
windowObjEventListeners.delete(eventKey);
}
}
},
once: (eventName, callback) => {
const eventKey = generateEventKey(eventName, winId);
const onceCallback = (...args) => {
callback(...args);
(windowObjEventListeners.get(eventKey) || {delete:()=>{}}).delete(onceCallback);
};
if (!windowObjEventListeners.has(eventKey)) {
windowObjEventListeners.set(eventKey, new Set());
}
windowObjEventListeners.get(eventKey).add(onceCallback);
}
};
// 为每个属性定义 getter
windowProperties.forEach(prop => {
Object.defineProperty(obj, prop, {
enumerable: true,
configurable: true,
get: async () => {
const response = await ipcRenderer.invoke('window-obj-action', {
method: prop,
winId: winId,
type: 'property'
});
if (response.success) {
return response.result;
} else {
throw new Error(response.error);
}
}
});
});
windowMethods.forEach(method => {
Object.defineProperty(obj, method, {
enumerable: true,
configurable: true,
value: async (...args) => {
const response = await ipcRenderer.invoke('window-obj-action', {
method,
winId: winId,
type: 'method',
args
});
if (response.success) {
return response.result;
} else {
throw new Error(response.error);
}
}
});
});
return obj;
} else {
throw new Error(response.error);
}
},
// 添加模态窗口方法
openModalWindow: async (parentWinId, windowPath, options = {}) => {
let response = await ipcRenderer.invoke('window-manager-action', {
method: '_openModalWindow',
args: [parentWinId, windowPath, options]
});
// 复用现有的窗口属性和方法处理逻辑
let windowProperties = [
// 'webContents',
'id', 'tabbingIdentifier', 'autoHideMenuBar', 'simpleFullScreen', 'fullScreen',
'focusable',
'visibleOnAllWorkspaces', 'shadow',
'menuBarVisible', 'kiosk',
'documentEdited', 'representedFilename', 'title', 'minimizable',
'maximizable', 'fullScreenable', 'resizable', 'closable', 'movable',
'excludedFromShownWindowsMenu', 'accessibleTitle'
];
let windowMethods = ["destroy", "close", "focus", "blur", "isFocused", "isDestroyed", "show", "showInactive", "hide", "isVisible", "isModal", "maximize", "unmaximize",
"isMaximized", "minimize", "restore", "isMinimized", "setFullScreen", "isFullScreen", "setSimpleFullScreen", "isSimpleFullScreen", "isNormal", "setAspectRatio",
"setBackgroundColor", "previewFile", "closeFilePreview", "setBounds", "getBounds", "getBackgroundColor", "setContentBounds", "getContentBounds", "getNormalBounds",
"setEnabled", "isEnabled", "setSize", "getSize", "setContentSize", "getContentSize", "setMinimumSize", "getMinimumSize", "setMaximumSize", "getMaximumSize",
"setResizable", "isResizable", "setMovable", "isMovable", "setMinimizable", "isMinimizable", "setMaximizable", "isMaximizable", "setFullScreenable",
"isFullScreenable", "setClosable", "isClosable", "setHiddenInMissionControl", "isHiddenInMissionControl", "setAlwaysOnTop", "isAlwaysOnTop", "moveAbove", "moveTop",
"center", "setPosition", "getPosition", "setTitle", "getTitle", "setSheetOffset", "flashFrame", "setSkipTaskbar", "setKiosk", "isKiosk", "isTabletMode",
"getMediaSourceId", "getNativeWindowHandle", "hookWindowMessage", "isWindowMessageHooked", "unhookWindowMessage", "unhookAllWindowMessages",
"setRepresentedFilename", "getRepresentedFilename", "setDocumentEdited", "isDocumentEdited", "focusOnWebView", "blurWebView", "capturePage", "loadURL", "loadFile",
"reload", "setMenu", "removeMenu", "setProgressBar", "setOverlayIcon", "invalidateShadow", "setHasShadow", "hasShadow", "setOpacity", "getOpacity", "setShape",
"setThumbarButtons", "setThumbnailClip", "setThumbnailToolTip", "setAppDetails", "showDefinitionForSelection", "setIcon", "setWindowButtonVisibility",
"setAutoHideMenuBar", "isMenuBarAutoHide", "setMenuBarVisibility", "isMenuBarVisible", "setVisibleOnAllWorkspaces", "isVisibleOnAllWorkspaces",
"setIgnoreMouseEvents", "setContentProtection", "setFocusable", "isFocusable", "setParentWindow", "getParentWindow", "getChildWindows", "setAutoHideCursor",
"selectPreviousTab", "selectNextTab", "showAllTabs", "mergeAllWindows", "moveTabToNewWindow", "toggleTabBar", "addTabbedWindow", "setVibrancy",
"setBackgroundMaterial", "setWindowButtonPosition", "getWindowButtonPosition", "setTouchBar", "setBrowserView", "getBrowserView", "addBrowserView",
"removeBrowserView", "setTopBrowserView", "getBrowserViews", "setTitleBarOverlay"
];
if (response.success) {
let winId = response.result.winId;
const obj = {
...response.result,
webContents: {
// 复用现有的事件处理方法
on: (eventName, callback) => {
const eventKey = generateEventKey(eventName, winId);
if (!windowEventListeners.has(eventKey)) {
windowEventListeners.set(eventKey, new Set());
}
windowEventListeners.get(eventKey).add(callback);
},
off: (eventName, callback) => {
const eventKey = generateEventKey(eventName, winId);
const listeners = windowEventListeners.get(eventKey);
if (listeners) {
listeners.delete(callback);
if (listeners.size === 0) {
windowEventListeners.delete(eventKey);
}
}
},
once: (eventName, callback) => {
const eventKey = generateEventKey(eventName, winId);
const onceCallback = (...args) => {
callback(...args);
(windowEventListeners.get(eventKey) || {delete:()=>{}}).delete(onceCallback);
};
if (!windowEventListeners.has(eventKey)) {
windowEventListeners.set(eventKey, new Set());
}
windowEventListeners.get(eventKey).add(onceCallback);
},
},
on: (eventName, callback) => {
const eventKey = generateEventKey(eventName, winId);
if (!windowObjEventListeners.has(eventKey)) {
windowObjEventListeners.set(eventKey, new Set());
}
windowObjEventListeners.get(eventKey).add(callback);
},
off: (eventName, callback) => {
const eventKey = generateEventKey(eventName, winId);
const listeners = windowObjEventListeners.get(eventKey);
if (listeners) {
listeners.delete(callback);
if (listeners.size === 0) {
windowObjEventListeners.delete(eventKey);
}
}
},
once: (eventName, callback) => {
const eventKey = generateEventKey(eventName, winId);
const onceCallback = (...args) => {
callback(...args);
(windowObjEventListeners.get(eventKey) || {delete:()=>{}}).delete(onceCallback);
};
if (!windowObjEventListeners.has(eventKey)) {
windowObjEventListeners.set(eventKey, new Set());
}
windowObjEventListeners.get(eventKey).add(onceCallback);
}
};
// 复用现有的属性和方法定义逻辑
windowProperties.forEach(prop => {
Object.defineProperty(obj, prop, {
enumerable: true,
configurable: true,
get: async () => {
const response = await ipcRenderer.invoke('window-obj-action', {
method: prop,
winId: winId,
type: 'property'
});
if (response.success) {
return response.result;
} else {
throw new Error(response.error);
}
}
});
});
windowMethods.forEach(method => {
Object.defineProperty(obj, method, {
enumerable: true,
configurable: true,
value: async (...args) => {
const response = await ipcRenderer.invoke('window-obj-action', {
method,
winId: winId,
type: 'method',
args
});
if (response.success) {
return response.result;
} else {
throw new Error(response.error);
}
}
});
});
return obj;
} else {
throw new Error(response.error);
}
},
getAllWindow: async (...args) => {
let response = await ipcRenderer.invoke('window-manager-action', {
method: '_getAllWindow',
args
});
if (response.success) {
return response.result;
} else {
throw new Error(response.error);
}
},
getWindowInfo: async (...args) => {
let response = await ipcRenderer.invoke('window-manager-action', {
method: '_getWindowInfo',
args
});
if (response.success) {
return response.result;
} else {
throw new Error(response.error);
}
},
getWindow: (__winId) => {
let winId = __winId ? __winId : _winId;
const windowProperties = [
// 'webContents',
'id', 'tabbingIdentifier', 'autoHideMenuBar', 'simpleFullScreen', 'fullScreen',
'focusable',
'visibleOnAllWorkspaces', 'shadow',
'menuBarVisible', 'kiosk',
'documentEdited', 'representedFilename', 'title', 'minimizable',
'maximizable', 'fullScreenable', 'resizable', 'closable', 'movable',
'excludedFromShownWindowsMenu', 'accessibleTitle'
];
const windowMethods = ["destroy", "close", "focus", "blur", "isFocused", "isDestroyed", "show", "showInactive", "hide", "isVisible", "isModal", "maximize", "unmaximize",
"isMaximized", "minimize", "restore", "isMinimized", "setFullScreen", "isFullScreen", "setSimpleFullScreen", "isSimpleFullScreen", "isNormal", "setAspectRatio",
"setBackgroundColor", "previewFile", "closeFilePreview", "setBounds", "getBounds", "getBackgroundColor", "setContentBounds", "getContentBounds", "getNormalBounds",
"setEnabled", "isEnabled", "setSize", "getSize", "setContentSize", "getContentSize", "setMinimumSize", "getMinimumSize", "setMaximumSize", "getMaximumSize",
"setResizable", "isResizable", "setMovable", "isMovable", "setMinimizable", "isMinimizable", "setMaximizable", "isMaximizable", "setFullScreenable",
"isFullScreenable", "setClosable", "isClosable", "setHiddenInMissionControl", "isHiddenInMissionControl", "setAlwaysOnTop", "isAlwaysOnTop", "moveAbove", "moveTop",
"center", "setPosition", "getPosition", "setTitle", "getTitle", "setSheetOffset", "flashFrame", "setSkipTaskbar", "setKiosk", "isKiosk", "isTabletMode",
"getMediaSourceId", "getNativeWindowHandle", "hookWindowMessage", "isWindowMessageHooked", "unhookWindowMessage", "unhookAllWindowMessages",
"setRepresentedFilename", "getRepresentedFilename", "setDocumentEdited", "isDocumentEdited", "focusOnWebView", "blurWebView", "capturePage", "loadURL", "loadFile",
"reload", "setMenu", "removeMenu", "setProgressBar", "setOverlayIcon", "invalidateShadow", "setHasShadow", "hasShadow", "setOpacity", "getOpacity", "setShape",
"setThumbarButtons", "setThumbnailClip", "setThumbnailToolTip", "setAppDetails", "showDefinitionForSelection", "setIcon", "setWindowButtonVisibility",
"setAutoHideMenuBar", "isMenuBarAutoHide", "setMenuBarVisibility", "isMenuBarVisible", "setVisibleOnAllWorkspaces", "isVisibleOnAllWorkspaces",
"setIgnoreMouseEvents", "setContentProtection", "setFocusable", "isFocusable", "setParentWindow", "getParentWindow", "getChildWindows", "setAutoHideCursor",
"selectPreviousTab", "selectNextTab", "showAllTabs", "mergeAllWindows", "moveTabToNewWindow", "toggleTabBar", "addTabbedWindow", "setVibrancy",
"setBackgroundMaterial", "setWindowButtonPosition", "getWindowButtonPosition", "setTouchBar", "setBrowserView", "getBrowserView", "addBrowserView",
"removeBrowserView", "setTopBrowserView", "getBrowserViews", "setTitleBarOverlay"]
const obj = {
webContents: {
// 添加窗口事件监听方法
on: (eventName, callback) => {
const eventKey = generateEventKey(eventName, winId);
if (!windowEventListeners.has(eventKey)) {
windowEventListeners.set(eventKey, new Set());
}
windowEventListeners.get(eventKey).add(callback);
},
// 移除窗口事件监听方法
off: (eventName, callback) => {
const eventKey = generateEventKey(eventName, winId);
const listeners = windowEventListeners.get(eventKey);
if (listeners) {
listeners.delete(callback);
if (listeners.size === 0) {
windowEventListeners.delete(eventKey);
}
}
},
// 一次性事件监听
once: (eventName, callback) => {
const eventKey = generateEventKey(eventName, winId);
const onceCallback = (...args) => {
callback(...args);
(windowEventListeners.get(eventKey) || {delete:()=>{}}).delete(onceCallback);
};
if (!windowEventListeners.has(eventKey)) {
windowEventListeners.set(eventKey, new Set());
}
windowEventListeners.get(eventKey).add(onceCallback);
},
},
// 添加窗口对象的事件处理方法
on: (eventName, callback) => {
const eventKey = generateEventKey(eventName, winId);
if (!windowObjEventListeners.has(eventKey)) {
windowObjEventListeners.set(eventKey, new Set());
}
windowObjEventListeners.get(eventKey).add(callback);
},
off: (eventName, callback) => {
const eventKey = generateEventKey(eventName, winId);
const listeners = windowObjEventListeners.get(eventKey);
if (listeners) {
listeners.delete(callback);
if (listeners.size === 0) {
windowObjEventListeners.delete(eventKey);
}
}
},
once: (eventName, callback) => {
const eventKey = generateEventKey(eventName, winId);
const onceCallback = (...args) => {
callback(...args);
(windowObjEventListeners.get(eventKey) || {delete:()=>{}}).delete(onceCallback);
};
if (!windowObjEventListeners.has(eventKey)) {
windowObjEventListeners.set(eventKey, new Set());
}
windowObjEventListeners.get(eventKey).add(onceCallback);
}
};
// 为每个属性定义 getter
windowProperties.forEach(prop => {
Object.defineProperty(obj, prop, {
enumerable: true,
configurable: true,
get: async () => {
const response = await ipcRenderer.invoke('window-obj-action', {
method: prop,
winId: winId,
type: 'property'
});
if (response.success) {
return response.result;
} else {
throw new Error(response.error);
}
}
});
});
windowMethods.forEach(method => {
Object.defineProperty(obj, method, {
enumerable: true,
configurable: true,
value: async (...args) => {
const response = await ipcRenderer.invoke('window-obj-action', {
method,
winId: winId,
type: 'method',
args
});
if (response.success) {
return response.result;
} else {
throw new Error(response.error);
}
}
});
});
return obj;
},
// 发送消息到指定窗口
sendToWindow: async (winId, eventName, data) => {
let response = await ipcRenderer.invoke('window-manager-action', {
method: 'sendToWindow',
args: [winId, eventName, data]
});
return response.success;
},
// 广播消息
broadcast: async (channel, data, excludeWinIds = []) => {
let response = await ipcRenderer.invoke('window-manager-action', {
method: 'broadcast',
args: [channel, data, excludeWinIds]
});
return response.success;
},
// 监听事件
on: (eventName, callback) => {
if (!windowEventListeners.has(eventName)) {
windowEventListeners.set(eventName, new Set());
}
windowEventListeners.get(eventName).add(callback);
},
// 移除事件监听
off: (eventName, callback) => {
const listeners = windowEventListeners.get(eventName);
if (listeners) {
listeners.delete(callback);
if (listeners.size === 0) {
windowEventListeners.delete(eventName);
}
}
},
// 一次性事件监听
once: (eventName, callback) => {
const onceCallback = (...args) => {
callback(...args);
(windowEventListeners.get(eventName) || {delete:()=>{}}).delete(onceCallback);
};
if (!windowEventListeners.has(eventName)) {
windowEventListeners.set(eventName, new Set());
}
windowEventListeners.get(eventName).add(onceCallback);
}
}
contextBridge.exposeInMainWorld('windowManager', windowManager);
contextBridge.exposeInMainWorld('openWindow', async (windowId, options = {}) => {
try {
const response = await ipcRenderer.invoke('window-manager-action', {
method: '_openWindow',
args: [windowId, options]
});
if (response.success) {
return response.result;
} else {
throw new Error(response.error);
}
} catch (error) {
throw error;
}
});
contextBridge.exposeInMainWorld('isMixone', true);
function parseJsObjectString(str) {
// 移除可能的空格和换行
str = str.trim();
// 检查是否是有效的JSON(以{开头,以}结尾)
if ((str.startsWith('{') && str.endsWith('}')) ||
(str.startsWith('[') && str.endsWith(']'))) {
try {
return JSON.parse(str);
} catch (e) {
// 如果JSON.parse失败,继续下面的方法
}
}
// 对于非标准JSON的对象字面量
try {
// 使用Function构造函数而不直接eval
return new Function('return ' + str + ';')();
} catch (e) {
console.error('解析失败:', e);
return null;
}
}
function getAtagNativeTarge(id){
let aObj = document.querySelector(`[data-a-id=${id}]`);
let nativeTarget = aObj ? aObj.getAttribute('native-target') : ''
let options = {}
try {
options = aObj ? (aObj.getAttribute('native-options') ? parseJsObjectString(aObj.getAttribute('native-options')) : {}) : {};
} catch (error) {
console.error('Failed to parse native-options:', error);
options = {}
}
return {nativeTarget, options}
}
function isHttpProtocol(href){
return ['http://','https://'].find(item => href.startsWith(item)) ? true : false
}
function parseUrl(urlStr) {
let obj = new URL(urlStr);
obj.port = obj.port || '80';
return obj;
}
contextBridge.exposeInMainWorld('autoHref', async (id, href) => {
console.log('href');
console.log(href);
let {nativeTarget, options} = getAtagNativeTarge(id);
//file:///E:/work/electron/demo-vue3-5/dist/packager/win-unpacked/resources/app.asar/out/build/windows/index.html
//全文件,并且不是内部文件
if(href.startsWith('file:///') && href.indexOf('/out/build/windows/')===-1){//todo
console.warn('You have opened an uncontrolled file')
if(nativeTarget == '_window'){
await windowManager.openWindow(href, options);
} else {
// window.location.href = href
}
} else if(href.startsWith('file:///') && href.indexOf('/out/build/windows/')>-1){//内部文件
console.warn('You have opened an internal file')
if(nativeTarget == '_window'){
// let windowStartIndex = href.indexOf('/out/build/windows/') + 18;
// let windowName = href.substring(windowStartIndex);
await windowManager.openWindow(href, options);
} else {
// window.location.href = href
}
} else if(isHttpProtocol(href)){
let urlObj = parseUrl(href);
let visitUrlObj = parseUrl(window.location.href);
if(urlObj.hostname == 'localhost' && visitUrlObj.hostname == 'localhost' && urlObj.port == visitUrlObj.port){
if(nativeTarget == '_window'){//把它转换成 文件全路径,再转换成windowName。
await windowManager.openWindow(href, options);
} else {
// window.location.href = href
}
} else {//其他http直接根据target跳
if(nativeTarget == '_window'){
await windowManager.openWindow(href, options);
} else {
// window.location.href = href
}
}
} else {
if (window.location.protocol == 'file:') {
let endIndex = window.location.href.indexOf('/out/build/windows') + 10;
// let rootDir = window.location.href.substring(0, endIndex)
let windowsDir = window.location.href.substring(0, endIndex + 8)
let _href
if (href.startsWith('/')) {
_href = windowsDir + href;
} else {
_href = resolveRelativePath(window.location.href, href);
}
if(nativeTarget=='_window'){
let windowStartIndex = _href.indexOf('/out/build/windows/') + 18;
let windowName = _href.substring(windowStartIndex);
await windowManager.openWindow(_href, options);
} else {
window.location.href = _href;
}
} else if(['http:','https:'].includes(window.location.protocol)) {//http
if(nativeTarget=='_window'){
// 因为不认识以/开头的,所以要把/开头的请求前加上 当前协议和host。
let _href
if(href.startsWith('/')){
_href = window.location.protocol + '//' + window.location.host + href;
}else{
// 是否是文件或目录。
let urlObj = parseUrl(window.location.href);
if(urlObj.pathname.endsWith('/')){
_href = window.location.protocol + '//' + window.location.host + urlObj.pathname + href;
} else {
// 不是目录,需要去掉文件名。
let fileName = urlObj.pathname.substring(urlObj.pathname.lastIndexOf('/') + 1);
// _href = window.location.protocol + '//' + window.location.host + urlObj.pathname.replace(fileName, '') + href; resolveRelativePath()
_href = resolveRelativePath(window.location.protocol + '//' + window.location.host + urlObj.pathname.replace(fileName, ''), href)
}
}
await windowManager.openWindow(_href, options);
} else {
window.location.href = href;//支持以 / 、../、./ 等。
}
}
}
});