|
|
@@ -1,4 +1,4 @@
|
|
|
-import { app, shell, BrowserWindow, ipcMain, Tray, Menu, nativeImage, WebContentsView, IpcMainEvent, dialog, screen } from 'electron'
|
|
|
+import { app, shell, session, BrowserWindow, ipcMain, Tray, Menu, nativeImage, WebContentsView, dialog, screen, type WebContents } from 'electron'
|
|
|
import { basename, extname, join } from 'path'
|
|
|
import { fileURLToPath } from 'url'
|
|
|
import { autoUpdater } from 'electron-updater'
|
|
|
@@ -180,6 +180,39 @@ async function downloadMediaToTempAndOpen(url: string, suggestedName: string): P
|
|
|
if (err) throw new Error(err)
|
|
|
}
|
|
|
|
|
|
+/** 与统一登录/开放平台同一源,应用中心内嵌页使用的 localStorage 键为 `token` */
|
|
|
+const API_HNYUNZHU_ORIGIN = 'https://api.hnyunzhu.com'
|
|
|
+
|
|
|
+/** 应用中心内嵌标签页专用分区;与主窗口默认 session 隔离,登出时可整体 clearStorage 而不影响主应用 */
|
|
|
+const INTERNAL_BROWSER_PARTITION = 'persist:yunzhu-app-center'
|
|
|
+
|
|
|
+function getInternalBrowserSession() {
|
|
|
+ return session.fromPartition(INTERNAL_BROWSER_PARTITION)
|
|
|
+}
|
|
|
+
|
|
|
+let browserApiHnyunzhuToken: string | null = null
|
|
|
+
|
|
|
+function isApiHnyunzhuUrl(url: string): boolean {
|
|
|
+ if (!url || url === 'about:blank') return false
|
|
|
+ try {
|
|
|
+ return new URL(url).origin === API_HNYUNZHU_ORIGIN
|
|
|
+ } catch {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function injectTokenForApiOrigin(wc: WebContents): void {
|
|
|
+ if (wc.isDestroyed()) return
|
|
|
+ const url = wc.getURL()
|
|
|
+ if (!isApiHnyunzhuUrl(url)) return
|
|
|
+ if (browserApiHnyunzhuToken) {
|
|
|
+ const safe = JSON.stringify(browserApiHnyunzhuToken)
|
|
|
+ void wc.executeJavaScript(`try{localStorage.setItem('token',${safe});}catch(e){}`, true)
|
|
|
+ } else {
|
|
|
+ void wc.executeJavaScript(`try{localStorage.removeItem('token');}catch(e){}`, true)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
// --- 浏览器视图管理器 ---
|
|
|
interface TabInfo {
|
|
|
id: string
|
|
|
@@ -201,6 +234,7 @@ class ViewManager {
|
|
|
createTab(url: string, active: boolean = true): string {
|
|
|
const view = new WebContentsView({
|
|
|
webPreferences: {
|
|
|
+ partition: INTERNAL_BROWSER_PARTITION,
|
|
|
sandbox: false,
|
|
|
nodeIntegration: false,
|
|
|
contextIsolation: true,
|
|
|
@@ -239,6 +273,10 @@ class ViewManager {
|
|
|
}
|
|
|
})
|
|
|
|
|
|
+ view.webContents.on('did-finish-load', () => {
|
|
|
+ injectTokenForApiOrigin(view.webContents)
|
|
|
+ })
|
|
|
+
|
|
|
view.webContents.setWindowOpenHandler((details) => {
|
|
|
if (!isHttpUrl(details.url)) {
|
|
|
return { action: 'allow' }
|
|
|
@@ -334,6 +372,32 @@ class ViewManager {
|
|
|
}))
|
|
|
}
|
|
|
|
|
|
+ /** 对当前已打开且为 api 源的内嵌页同步/清除 `localStorage.token`(登入、换号、登出时调用) */
|
|
|
+ syncTokenToAllTabs(): void {
|
|
|
+ this.tabs.forEach((tab) => {
|
|
|
+ try {
|
|
|
+ if (!tab.view.webContents.isDestroyed()) {
|
|
|
+ injectTokenForApiOrigin(tab.view.webContents)
|
|
|
+ }
|
|
|
+ } catch {
|
|
|
+ // ignore
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 全量清存储后刷新各标签,避免仍停留在旧页面内存态 */
|
|
|
+ reloadAllTabs(): void {
|
|
|
+ this.tabs.forEach((tab) => {
|
|
|
+ try {
|
|
|
+ if (!tab.view.webContents.isDestroyed()) {
|
|
|
+ tab.view.webContents.reloadIgnoringCache()
|
|
|
+ }
|
|
|
+ } catch {
|
|
|
+ // ignore
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
destroy() {
|
|
|
this.tabs.forEach(tab => {
|
|
|
try {
|
|
|
@@ -999,21 +1063,42 @@ app.whenReady().then(() => {
|
|
|
updateUnreadStatus()
|
|
|
})
|
|
|
|
|
|
- ipcMain.on('login-success', () => {
|
|
|
+ ipcMain.on('login-success', (event, payload?: { token?: string }) => {
|
|
|
+ if (!mainWindow || mainWindow.isDestroyed() || event.sender.id !== mainWindow.webContents.id) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (typeof payload?.token === 'string' && payload.token.length > 0) {
|
|
|
+ browserApiHnyunzhuToken = payload.token
|
|
|
+ viewManager?.syncTokenToAllTabs()
|
|
|
+ }
|
|
|
// 登录成功后清空消息中心未读,避免切换账号展示上个账号的残留
|
|
|
unreadMap.clear()
|
|
|
appUnreadTotalFromApi = null
|
|
|
updateUnreadStatus()
|
|
|
- if (mainWindow) {
|
|
|
- mainWindow.setSize(1000, 700)
|
|
|
- mainWindow.setResizable(true)
|
|
|
- mainWindow.center()
|
|
|
- mainWindow.setTitleBarOverlay({
|
|
|
- color: '#f5f5f5',
|
|
|
- symbolColor: '#747474',
|
|
|
- height: 30
|
|
|
- })
|
|
|
+ mainWindow.setSize(1000, 700)
|
|
|
+ mainWindow.setResizable(true)
|
|
|
+ mainWindow.center()
|
|
|
+ mainWindow.setTitleBarOverlay({
|
|
|
+ color: '#f5f5f5',
|
|
|
+ symbolColor: '#747474',
|
|
|
+ height: 30
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ /** 主窗口退出登录:清主进程 token,并清除应用中心分区下全部站点存储后刷新内嵌页 */
|
|
|
+ ipcMain.on('browser-auth-logout', (event) => {
|
|
|
+ if (!mainWindow || mainWindow.isDestroyed() || event.sender.id !== mainWindow.webContents.id) {
|
|
|
+ return
|
|
|
}
|
|
|
+ browserApiHnyunzhuToken = null
|
|
|
+ void getInternalBrowserSession()
|
|
|
+ .clearStorageData()
|
|
|
+ .then(() => {
|
|
|
+ viewManager?.reloadAllTabs()
|
|
|
+ })
|
|
|
+ .catch((e) => {
|
|
|
+ console.error('clearStorageData (internal browser)', e)
|
|
|
+ })
|
|
|
})
|
|
|
|
|
|
ipcMain.on('start-notification', (_, data: { id: number, name: string, content: string }) => {
|