Browse Source

V1.0.14 oidc自动登录

liuq 1 month ago
parent
commit
363aa6dcac
3 changed files with 99 additions and 13 deletions
  1. 96 11
      src/main/index.ts
  2. 1 1
      src/renderer/src/components/Login.tsx
  3. 2 1
      src/renderer/src/hooks/useAuth.ts

+ 96 - 11
src/main/index.ts

@@ -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 }) => {

+ 1 - 1
src/renderer/src/components/Login.tsx

@@ -232,7 +232,7 @@ const Login: React.FC<LoginProps> = ({ onLogin, bannerMessage }) => {
 
       // 通知 Electron 主进程(可选)
       if (window.electron && window.electron.ipcRenderer) {
-        window.electron.ipcRenderer.send('login-success')
+        window.electron.ipcRenderer.send('login-success', { token: data.access_token })
         logger.info('Login: Sent login-success event to Electron main process')
       }
       

+ 2 - 1
src/renderer/src/hooks/useAuth.ts

@@ -19,6 +19,7 @@ export function useAuth() {
     // 退出登录时清空消息中心未读,避免切换账号后仍显示上个账号的未读
     if (window.electron && window.electron.ipcRenderer) {
       window.electron.ipcRenderer.send('clear-all-unread')
+      window.electron.ipcRenderer.send('browser-auth-logout')
     }
     localStorage.removeItem('auth_token')
     localStorage.removeItem('user_id')
@@ -93,7 +94,7 @@ export function useAuth() {
           })
 
           if (window.electron?.ipcRenderer) {
-            window.electron.ipcRenderer.send('login-success')
+            window.electron.ipcRenderer.send('login-success', { token: savedToken })
             logger.info('useAuth: Sent login-success to main process (session restore)')
           }
         } else {