- 基于 openim-electron-demo 改造:工号+密码登录(自研账号服务)、会话列表/聊天窗/通讯录界面重做
- 文字/语音/文件/图片消息、文件拖拽发送、表情面板、一对一语音通话(LiveKit)
- RTC 令牌对接账号服务带鉴权版 /api/rtc_token(Bearer imToken + {room,identity})
- account-service: CORS Allow-Headers 补 authorization(浏览器/渲染进程跨域调 rtc_token 需要)
- 附本地 mock 账号服务(scripts/mock-account-server.js)与截图联调脚本
260 lines
7.0 KiB
TypeScript
260 lines
7.0 KiB
TypeScript
import { join } from "node:path";
|
|
import { BrowserWindow, dialog, shell } from "electron";
|
|
import { isLinux, isMac, isWin } from "../utils";
|
|
import { destroyTray } from "./trayManage";
|
|
import { getIsForceQuit } from "./appManage";
|
|
import { registerShortcuts, unregisterShortcuts } from "./shortcutManage";
|
|
import { initIMSDK } from "../utils/imsdk";
|
|
import OpenIMSDKMain from "@openim/electron-client-sdk";
|
|
|
|
const url = process.env.VITE_DEV_SERVER_URL;
|
|
let mainWindow: BrowserWindow | null = null;
|
|
let splashWindow: BrowserWindow | null = null;
|
|
let sdkInstance: OpenIMSDKMain | null = null;
|
|
|
|
function createSplashWindow() {
|
|
splashWindow = new BrowserWindow({
|
|
frame: false,
|
|
width: 200,
|
|
height: 200,
|
|
resizable: false,
|
|
transparent: true,
|
|
});
|
|
splashWindow.loadFile(global.pathConfig.splashHtml);
|
|
splashWindow.on("closed", () => {
|
|
splashWindow = null;
|
|
});
|
|
}
|
|
|
|
export function createMainWindow() {
|
|
createSplashWindow();
|
|
mainWindow = new BrowserWindow({
|
|
title: "畅联",
|
|
icon: join(global.pathConfig.publicPath, "favicon.ico"),
|
|
frame: false,
|
|
show: false,
|
|
width: 1200,
|
|
height: 800,
|
|
minWidth: 1024,
|
|
minHeight: 726,
|
|
titleBarStyle: "hiddenInset",
|
|
webPreferences: {
|
|
preload: global.pathConfig.preload,
|
|
// Warning: Enable nodeIntegration and disable contextIsolation is not secure in production
|
|
// Consider using contextBridge.exposeInMainWorld
|
|
// Read more on https://www.electronjs.org/docs/latest/tutorial/context-isolation
|
|
nodeIntegration: false,
|
|
contextIsolation: true,
|
|
sandbox: false,
|
|
devTools: true,
|
|
webSecurity: false,
|
|
},
|
|
});
|
|
|
|
sdkInstance = initIMSDK(mainWindow.webContents);
|
|
|
|
if (process.env.VITE_DEV_SERVER_URL) {
|
|
// Open devTool if the app is not packaged
|
|
mainWindow.loadURL(url);
|
|
} else {
|
|
mainWindow.loadFile(global.pathConfig.indexHtml);
|
|
}
|
|
|
|
// Test actively push message to the Electron-Renderer
|
|
mainWindow.webContents.on("did-finish-load", () => {
|
|
mainWindow?.webContents.send("main-process-message", new Date().toLocaleString());
|
|
});
|
|
|
|
// // Make all links open with the browser, not with the application
|
|
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
|
|
if (url.startsWith("https:") || url.startsWith("http:")) shell.openExternal(url);
|
|
return { action: "deny" };
|
|
});
|
|
|
|
mainWindow.on("focus", () => {
|
|
mainWindow?.flashFrame(false);
|
|
registerShortcuts();
|
|
});
|
|
|
|
mainWindow.on("blur", () => {
|
|
unregisterShortcuts();
|
|
});
|
|
|
|
mainWindow.on("close", (e) => {
|
|
if (getIsForceQuit() || !mainWindow.isVisible()) {
|
|
mainWindow = null;
|
|
destroyTray();
|
|
} else {
|
|
e.preventDefault();
|
|
if (isMac && mainWindow.isFullScreen()) {
|
|
mainWindow.setFullScreen(false);
|
|
}
|
|
mainWindow?.hide();
|
|
}
|
|
});
|
|
return mainWindow;
|
|
}
|
|
|
|
export function splashEnd() {
|
|
splashWindow?.close();
|
|
mainWindow?.show();
|
|
}
|
|
|
|
// utils
|
|
export const isExistMainWindow = (): boolean =>
|
|
!!mainWindow && !mainWindow?.isDestroyed();
|
|
export const isShowMainWindow = (): boolean => {
|
|
if (!mainWindow) return false;
|
|
return mainWindow.isVisible() && (isWin ? true : mainWindow.isFocused());
|
|
};
|
|
|
|
export const closeWindow = () => {
|
|
if (!mainWindow) return;
|
|
mainWindow.close();
|
|
};
|
|
|
|
export const hotReload = () => {
|
|
if (!mainWindow) return;
|
|
mainWindow.reload();
|
|
};
|
|
|
|
export const sendEvent = (name: string, ...args: any[]) => {
|
|
if (!mainWindow) return;
|
|
mainWindow.webContents.send(name, ...args);
|
|
};
|
|
|
|
export const showSelectDialog = async (options: Electron.OpenDialogOptions) => {
|
|
if (!mainWindow) throw new Error("main window is undefined");
|
|
return await dialog.showOpenDialog(mainWindow, options);
|
|
};
|
|
export const showDialog = ({
|
|
type,
|
|
message,
|
|
detail,
|
|
}: Electron.MessageBoxSyncOptions) => {
|
|
if (!mainWindow) return;
|
|
dialog.showMessageBoxSync(mainWindow, {
|
|
type,
|
|
message,
|
|
detail,
|
|
});
|
|
};
|
|
export const showSaveDialog = async (options: Electron.SaveDialogOptions) => {
|
|
if (!mainWindow) throw new Error("main window is undefined");
|
|
return await dialog.showSaveDialog(mainWindow, options);
|
|
};
|
|
export const minimize = () => {
|
|
if (!mainWindow) return;
|
|
mainWindow.minimize();
|
|
};
|
|
export const updateMaximize = () => {
|
|
if (!mainWindow) return;
|
|
if (mainWindow.isMaximized()) {
|
|
mainWindow.unmaximize();
|
|
} else {
|
|
mainWindow.maximize();
|
|
}
|
|
};
|
|
export const toggleHide = () => {
|
|
if (!mainWindow) return;
|
|
mainWindow.isVisible() ? mainWindow.hide() : mainWindow.show();
|
|
};
|
|
export const toggleMinimize = () => {
|
|
if (!mainWindow) return;
|
|
if (mainWindow.isMinimized()) {
|
|
if (!mainWindow.isVisible()) {
|
|
mainWindow.show();
|
|
}
|
|
mainWindow.restore();
|
|
mainWindow.focus();
|
|
} else {
|
|
mainWindow.minimize();
|
|
}
|
|
};
|
|
export const showWindow = () => {
|
|
if (!mainWindow) return;
|
|
if (mainWindow.isMinimized()) {
|
|
mainWindow.restore();
|
|
}
|
|
if (mainWindow.isVisible()) {
|
|
mainWindow.focus();
|
|
} else {
|
|
mainWindow.show();
|
|
}
|
|
};
|
|
export const hideWindow = () => {
|
|
if (!mainWindow) return;
|
|
mainWindow.hide();
|
|
};
|
|
export const toggleWindowVisible = (visible: boolean) => {
|
|
if (!mainWindow) return;
|
|
const opacity = mainWindow.getOpacity() ? 0 : 1;
|
|
if (Boolean(opacity) !== visible) return;
|
|
mainWindow.setOpacity(opacity);
|
|
};
|
|
export const setProgressBar = (
|
|
progress: number,
|
|
options?: Electron.ProgressBarOptions,
|
|
) => {
|
|
if (!mainWindow) return;
|
|
mainWindow.setProgressBar(progress, options);
|
|
};
|
|
export const taskFlicker = () => {
|
|
if (
|
|
isMac ||
|
|
(mainWindow.isVisible() && mainWindow.isFocused() && !isExistMainWindow())
|
|
)
|
|
return;
|
|
mainWindow?.flashFrame(true);
|
|
};
|
|
export const setIgnoreMouseEvents = (
|
|
ignore: boolean,
|
|
options?: Electron.IgnoreMouseEventsOptions,
|
|
) => {
|
|
if (!mainWindow) return;
|
|
mainWindow.setIgnoreMouseEvents(ignore, options);
|
|
};
|
|
export const toggleDevTools = () => {
|
|
if (!mainWindow) return;
|
|
if (mainWindow.webContents.isDevToolsOpened()) {
|
|
mainWindow.webContents.closeDevTools();
|
|
} else {
|
|
mainWindow.webContents.openDevTools({
|
|
mode: "detach",
|
|
});
|
|
}
|
|
};
|
|
|
|
export const setFullScreen = (isFullscreen: boolean): boolean => {
|
|
if (!mainWindow) return false;
|
|
if (isLinux) {
|
|
// linux It needs to be resizable before it can be full screen
|
|
if (isFullscreen) {
|
|
mainWindow.setResizable(isFullscreen);
|
|
mainWindow.setFullScreen(isFullscreen);
|
|
} else {
|
|
mainWindow.setFullScreen(isFullscreen);
|
|
mainWindow.setResizable(isFullscreen);
|
|
}
|
|
} else {
|
|
mainWindow.setFullScreen(isFullscreen);
|
|
}
|
|
return isFullscreen;
|
|
};
|
|
|
|
export const clearCache = async () => {
|
|
if (!mainWindow) throw new Error("main window is undefined");
|
|
await mainWindow.webContents.session.clearCache();
|
|
await mainWindow.webContents.session.clearStorageData();
|
|
};
|
|
|
|
export const getCacheSize = async () => {
|
|
if (!mainWindow) throw new Error("main window is undefined");
|
|
return await mainWindow.webContents.session.getCacheSize();
|
|
};
|
|
|
|
export const getWebContents = (): Electron.WebContents => {
|
|
if (!mainWindow) throw new Error("main window is undefined");
|
|
return mainWindow.webContents;
|
|
};
|