Browse Source

重构项目架构并优化热更新功能

- 修改 `.vscode/settings.json` 中的 TypeScript 默认格式化工具为 Prettier,统一代码风格
- 在 `config/index.ts` 中添加 `update.hoturl` 配置项,用于指定热更新包下载地址
- 在 `package.json` 中新增 `base` 和 `helper` 工作区依赖,实现模块化拆分
- 移除 `src/common/lib/_Base.ts` 并将单例基类迁移至 `packages/base` 独立模块
- 重构热更新模块到 `packages/helper/updater`,增加下载进度事件通知功能
- 清理废弃的更新相关事件定义文件和冗余代码,保持代码整洁
- 将日志模块从 debug 切换为 logger 命名空间,统一日志管理
- 添加调试初始化脚本 `src/main/debug.ts`,优化日志记录机制
feat/优化
谢亚昕 3 days ago
parent
commit
a9de1ec525
  1. 2
      .vscode/settings.json
  2. 2
      config/index.ts
  3. 2
      package.json
  4. 27
      packages/base/index.ts
  5. 13
      packages/base/package.json
  6. 13
      packages/helper/package.json
  7. 7
      packages/helper/updater/common.ts
  8. 8
      packages/helper/updater/main/handler.ts
  9. 74
      packages/helper/updater/main/hot/download.ts
  10. 28
      packages/helper/updater/main/hot/index.ts
  11. 36
      packages/helper/updater/main/index.ts
  12. 0
      packages/helper/updater/renderer.ts
  13. 1
      packages/logger/main.ts
  14. 8
      packages/setting/main.ts
  15. 10
      pnpm-lock.yaml
  16. 7
      src/common/_ioc.main.ts
  17. 4
      src/common/event/PlatForm/index.ts
  18. 2
      src/common/event/Snippet/hook.ts
  19. 4
      src/common/event/Snippet/index.ts
  20. 4
      src/common/event/Tabs/index.ts
  21. 15
      src/common/event/Updater/index.ts
  22. 15
      src/common/event/Updater/main/command.ts
  23. 5
      src/common/event/common.ts
  24. 14
      src/common/event/update/index.ts
  25. 10
      src/common/event/update/main/command.ts
  26. 8
      src/common/event/update/main/index.ts
  27. 12
      src/common/lib/_Base.ts
  28. 7
      src/common/readme.md
  29. 14
      src/main/App.ts
  30. 3
      src/main/base/base.ts
  31. 60
      src/main/debug.ts
  32. 66
      src/main/index.ts
  33. 3
      src/main/modules/_ioc.ts
  34. 6
      src/main/modules/db/index.ts
  35. 8
      src/main/modules/tabs/Tab.ts
  36. 2
      src/main/modules/tabs/index.ts
  37. 3
      src/main/modules/window-manager/index.ts
  38. 35
      src/main/modules/zephyr/index.ts
  39. 2
      src/main/utils/index.ts
  40. 10
      tsconfig.node.json
  41. 9
      tsconfig.web.json

2
.vscode/settings.json

@ -1,6 +1,6 @@
{
"[typescript]": {
"editor.defaultFormatter": "vscode.typescript-language-features"
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"

2
config/index.ts

@ -12,6 +12,7 @@ export interface IDefaultConfig {
"common.theme": ThemeType
debug: LogLevel
"desktop:wallpaper": string
"update.hoturl": string
"update.repo"?: string
"update.owner"?: string
"update.allowDowngrade": boolean
@ -40,6 +41,7 @@ export default {
"editor.bg": "",
"editor.logoType": "logo",
"editor.fontFamily": "Cascadia Mono, Consolas, 'Courier New', monospace",
"update.hoturl": "https://alist.xieyaxin.top/d/%E8%B5%84%E6%BA%90/%E6%B5%8B%E8%AF%95%E6%96%87%E4%BB%B6.zip?sign=eqy35CR-J1SOQZz0iUN2P3B0BiyZPdYH0362nLXbUhE=:1749085071",
"update.repo": "wood-desktop",
"update.owner": "npmrun",
"update.allowDowngrade": false,

2
package.json

@ -50,6 +50,7 @@
"@vue/eslint-config-prettier": "^9.0.0",
"@vue/eslint-config-typescript": "^13.0.0",
"@vueuse/core": "^12.7.0",
"base": "workspace:*",
"debug": "^4.4.0",
"electron": "^31.7.7",
"electron-builder": "^24.13.3",
@ -57,6 +58,7 @@
"eslint": "^8.57.1",
"eslint-plugin-vue": "^9.32.0",
"extract-zip": "^2.0.1",
"helper": "workspace:*",
"locales": "workspace:*",
"lodash-es": "^4.17.21",
"logger": "workspace:^",

27
packages/base/index.ts

@ -0,0 +1,27 @@
// 抽象基类,使用泛型来正确推导子类类型
abstract class BaseSingleton {
private static _instance: any
public constructor() {
if (this.constructor === BaseSingleton) {
throw new Error("禁止直接实例化 BaseOne 抽象类")
}
if ((this.constructor as any)._instance) {
throw new Error("构造函数私有化失败,禁止重复 new")
}
// this.constructor 是子类,所以这里设为 instance
;(this.constructor as any)._instance = this
}
public static getInstance<T extends BaseSingleton>(this: new () => T): T {
const clazz = this as any as typeof BaseSingleton
if (!clazz._instance) {
clazz._instance = new this()
}
return clazz._instance as T
}
}
export { BaseSingleton }

13
packages/base/package.json

@ -0,0 +1,13 @@
{
"name": "base",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"packageManager": "pnpm@10.4.1"
}

13
packages/helper/package.json

@ -0,0 +1,13 @@
{
"name": "helper",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"packageManager": "pnpm@10.4.1"
}

7
packages/helper/updater/common.ts

@ -0,0 +1,7 @@
export const enum EventEnum {
UPDATE_PROGRESS = "update-progress",
}
export type EventMaps = {
[EventEnum.UPDATE_PROGRESS]: () => void
}

8
packages/helper/updater/main/handler.ts

@ -0,0 +1,8 @@
import { broadcast } from "main/utils"
import { EventEnum } from "../common"
export { EventEnum }
export function emit(key: EventEnum, ...args: any[]) {
broadcast(key, ...args)
}

74
packages/helper/updater/main/hot/download.ts

@ -0,0 +1,74 @@
type DownloadPercent = {
url: string
option?: object
onprocess?: (now: number, all: number) => void
onsuccess?: (data: any) => void
onerror?: (res: Response) => void
}
const RequestPercent = async ({
url = "",
option = {
headers: {
responseType: "arraybuffer",
},
},
onsuccess,
onerror,
onprocess,
}: DownloadPercent) => {
const response = (await fetch(url, option)) as any
if (!response.ok) {
onerror?.(response)
throw new Error(`下载失败`)
}
const reader = response?.body.getReader()
// 文件总长度
const contentLength = +response.headers.get("content-length")
let receivedLength = 0
const chunks: any[] = []
// eslint-disable-next-line no-constant-condition
while (true) {
const { done, value } = await reader.read()
if (done) {
break
}
chunks.push(value)
receivedLength += value.length
onprocess?.(receivedLength, contentLength)
}
// 这里的chunksAll 已经是ArrayBuffer的数据类型了,可以直接返回,也可以转为blob处理
const chunksAll = new Uint8Array(receivedLength)
let position = 0
for (const chunk of chunks) {
chunksAll.set(chunk, position)
position += chunk.length
}
onsuccess?.(chunksAll)
return chunksAll
}
export { RequestPercent }
export default RequestPercent
// RequestPercent({
// url: "http://117.21.250.136:9812/ZxqyGateway/biz/file/downApk/%E6%98%93%E4%BC%81%E6%95%B0%E8%BD%AC%E5%B9%B3%E5%8F%B0app-1.2.7.apk",
// option: {
// headers: {
// responseType: "arraybuffer",
// },
// },
// onerror: () => {},
// onsuccess: data => {
// fs.writeFileSync("./aaa.apk", Buffer.from(data))
// console.log("success", data)
// },
// onprocess: (receivedLength, contentLength) => {
// console.log(receivedLength, contentLength)
// },
// })

28
src/main/modules/updater/hot/index.ts → packages/helper/updater/main/hot/index.ts

@ -3,11 +3,13 @@ import fs from "node:fs"
import path from "node:path"
import os from "node:os"
import { app } from "electron"
import download from "./download"
import extract from "extract-zip"
import { emitHotUpdateReady } from "common/event/Update/main"
import _debug from "debug"
const debug = _debug("app:hot-updater")
import _logger from "logger/main"
import { emit, EventEnum } from "../handler"
const logger = _logger.createNamespace("hot-updater")
function getUpdateScriptTemplate() {
return process.platform === "win32"
@ -71,7 +73,7 @@ app.once("will-quit", event => {
})
// 下载热更新包
export async function fetchHotUpdatePackage(updatePackageUrl: string = "https://example.com/updates/latest.zip") {
export async function fetchHotUpdatePackage(updatePackageUrl: string) {
if (isReadyUpdate) return
// 清除临时目录
@ -86,24 +88,22 @@ export async function fetchHotUpdatePackage(updatePackageUrl: string = "https://
try {
// 使用 fetch 下载更新包
const response = await fetch(updatePackageUrl)
if (!response.ok) {
throw new Error(`下载失败: ${response.status} ${response.statusText}`)
}
// 将下载内容写入文件
const arrayBuffer = await response.arrayBuffer()
const arrayBuffer = await download({
url: updatePackageUrl,
onprocess(now, all) {
logger.debug(`下载进度: ${((now / all) * 100).toFixed(2)}%`)
emit(EventEnum.UPDATE_PROGRESS, { percent: (now / all) * 100, now, all })
},
})
fs.writeFileSync(downloadPath, Buffer.from(arrayBuffer))
// 解压更新包
await extract(downloadPath, { dir: updateTempDirPath })
// 删除下载的zip文件
fs.unlinkSync(downloadPath)
isReadyUpdate = true
emitHotUpdateReady()
} catch (error) {
debug("热更新包下载失败:", error)
logger.debug("热更新包下载失败:", error)
throw error
}
}

36
src/main/modules/updater/index.ts → packages/helper/updater/main/index.ts

@ -1,23 +1,22 @@
import pkg from "electron-updater"
import { app, dialog } from "electron"
import { injectable } from "inversify"
import BaseClass from "main/base/base"
// import { Setting } from "../setting"
import _debug from "debug"
import Setting from "setting/main"
import EventEmitter from "events"
import { BaseSingleton } from "base"
import { fetchHotUpdatePackage, flagNeedUpdate } from "./hot"
import Locales from "locales/main"
import _logger from "logger/main"
const debug = _debug("app:updater")
const logger = _logger.createNamespace("updater")
const { autoUpdater } = pkg
@injectable()
export class Updater extends BaseClass {
class _Updater extends BaseSingleton {
public events = new EventEmitter()
private timer: ReturnType<typeof setInterval> | null = null
// autoReplace = false
async triggerHotUpdate(autoReplace = false) {
await fetchHotUpdatePackage()
const url = Setting.values("update.hoturl")
await fetchHotUpdatePackage(url)
flagNeedUpdate()
if (!autoReplace) {
dialog.showMessageBox({
@ -31,42 +30,41 @@ export class Updater extends BaseClass {
constructor() {
super()
// 配置自动更新
autoUpdater.autoDownload = false
autoUpdater.autoInstallOnAppQuit = true
// 检查更新错误
autoUpdater.on("error", error => {
debug("Update error:", error)
logger.debug("Update error:", error)
})
// 检查更新
autoUpdater.on("checking-for-update", () => {
debug("Checking for updates...")
logger.debug("Checking for updates...")
})
// 有可用更新
autoUpdater.on("update-available", info => {
debug("Update available:", info)
logger.debug("Update available:", info)
this.promptUserToUpdate()
})
// 没有可用更新
autoUpdater.on("update-not-available", info => {
debug("Update not available:", info)
logger.debug("Update not available:", info)
})
// 更新下载进度
autoUpdater.on("download-progress", progressObj => {
debug(
logger.debug(
`Download speed: ${progressObj.bytesPerSecond} - Downloaded ${progressObj.percent}% (${progressObj.transferred}/${progressObj.total})`,
)
})
// 更新下载完成
autoUpdater.on("update-downloaded", info => {
debug("Update downloaded:", info)
logger.debug("Update downloaded:", info)
this.promptUserToInstall()
})
}
@ -95,9 +93,12 @@ export class Updater extends BaseClass {
if (app.isPackaged) {
try {
await autoUpdater.checkForUpdates()
logger.debug("Updater初始化检查成功.")
} catch (error) {
debug("Failed to check for updates:", error)
logger.debug("Failed to check for updates:", error)
}
} else {
logger.debug("正在开发模式,跳过更新检查.")
}
}
@ -130,4 +131,7 @@ export class Updater extends BaseClass {
}
}
const Updater = _Updater.getInstance()
export { Updater }
export default Updater

0
packages/helper/updater/renderer.ts

1
packages/logger/main.ts

@ -5,7 +5,6 @@ import setting from "setting/main"
import * as rfs from "rotating-file-stream"
import { LogLevel, LogLevelColor, LogLevelName } from "./common"
// 重置颜色的ANSI代码
const RESET_COLOR = "\x1b[0m"

8
packages/setting/main.ts

@ -4,9 +4,9 @@ import path from "path"
import { cloneDeep } from "lodash"
import Config from "config"
import type { IDefaultConfig } from "config"
import _debug from "debug"
import _logger from "logger/main"
const debug = _debug("app:setting")
const logger = _logger.createNamespace("setting")
type IConfig = IDefaultConfig
@ -59,7 +59,7 @@ function isEmptyDir(fPath: string) {
class SettingClass {
constructor() {
debug(`Setting inited`)
logger.debug(`Setting inited`)
this.init()
}
@ -122,7 +122,7 @@ class SettingClass {
}
}
init() {
debug(`位置:${this.#pathFile}`)
logger.debug(`位置:${this.#pathFile}`)
if (fs.pathExistsSync(this.#pathFile)) {
const confingPath = fs.readFileSync(this.#pathFile, { encoding: "utf8" })

10
pnpm-lock.yaml

@ -78,6 +78,9 @@ importers:
'@vueuse/core':
specifier: ^12.7.0
version: 12.7.0(typescript@5.7.3)
base:
specifier: workspace:*
version: link:packages/base
debug:
specifier: ^4.4.0
version: 4.4.0
@ -99,6 +102,9 @@ importers:
extract-zip:
specifier: ^2.0.1
version: 2.0.1
helper:
specifier: workspace:*
version: link:packages/helper
locales:
specifier: workspace:*
version: link:packages/locales
@ -178,6 +184,10 @@ importers:
specifier: ^2.1.10
version: 2.1.10(typescript@5.7.3)
packages/base: {}
packages/helper: {}
packages/locales: {}
packages/logger: {}

7
src/common/_ioc.main.ts

@ -12,7 +12,12 @@ const modules = new ContainerModule(bind => {
const CommandClass = (module as { default: any }).default
if (CommandClass) {
const className = CommandClass.name.replace("Command", "")
bind(className + "Command").to(CommandClass).inSingletonScope()
if (CommandClass["init"]) {
CommandClass["init"]()
}
bind(className + "Command")
.to(CommandClass)
.inSingletonScope()
}
})
})

4
src/common/event/PlatForm/index.ts

@ -1,8 +1,8 @@
import { _Base } from "common/lib/_Base"
import { ApiFactory } from "common/lib/abstract"
import { BaseSingleton } from "base"
import { LogLevel } from "packages/logger/common"
class PlatForm extends _Base {
class PlatForm extends BaseSingleton {
constructor() {
super()
}

2
src/common/event/Snippet/hook.ts

@ -1,5 +1,5 @@
import { Snippet } from "."
export function useSnippet() {
return Snippet.getInstance<Snippet>()
return Snippet.getInstance()
}

4
src/common/event/Snippet/index.ts

@ -1,7 +1,7 @@
import { _Base } from "common/lib/_Base"
import { BaseSingleton } from "base"
import { ApiFactory } from "common/lib/abstract"
class Snippet extends _Base {
class Snippet extends BaseSingleton {
constructor() {
super()
}

4
src/common/event/Tabs/index.ts

@ -1,6 +1,6 @@
import { _Base } from "../../lib/_Base"
import { BaseSingleton } from "base"
export class Tabs extends _Base {
export class Tabs extends BaseSingleton {
constructor() {
super()
}

15
src/common/event/Updater/index.ts

@ -0,0 +1,15 @@
import { EventEnum } from "helper/updater/common"
const curProgress = ref(0)
api.on(EventEnum.UPDATE_PROGRESS, ({ percent, now, all }) => {
curProgress.value = percent
})
function useUpdate() {
return {
curProgress,
}
}
export { useUpdate }

15
src/common/event/Updater/main/command.ts

@ -0,0 +1,15 @@
import Updater from "helper/updater/main"
import _logger from "logger/main"
const logger = _logger.createNamespace("UpdaterCommand")
export default class UpdaterCommand {
static init() {
// 命令初始化
logger.debug("UpdaterCommand init")
}
async triggerHotUpdate() {
Updater.triggerHotUpdate()
}
}

5
src/common/event/common.ts

@ -1,5 +0,0 @@
const keys = ["hot-update-ready"] as const
type AllKeys = (typeof keys)[number]
export type { AllKeys }

14
src/common/event/update/index.ts

@ -1,14 +0,0 @@
// import type { AllKeys } from "../common"
const curProgress = ref(0)
// api.on<AllKeys>("progress", () => {
// curProgress.value = 10
// })
function useUpdate() {
return {
curProgress,
}
}
export { useUpdate }

10
src/common/event/update/main/command.ts

@ -1,10 +0,0 @@
import { inject } from "inversify"
import Updater from "main/modules/updater"
export default class PlatFormCommand {
constructor(@inject(Updater) private _Updater: Updater) {}
async triggerHotUpdate() {
await this._Updater.triggerHotUpdate()
}
}

8
src/common/event/update/main/index.ts

@ -1,8 +0,0 @@
import { broadcast } from "main/utils"
import { AllKeys } from "common/event/common"
function emitHotUpdateReady(...argu) {
broadcast<AllKeys>("hot-update-ready", ...argu)
}
export { emitHotUpdateReady }

12
src/common/lib/_Base.ts

@ -1,12 +0,0 @@
export abstract class _Base {
static instance
static getInstance<T>(): T {
if (!this.instance) {
// 如果实例不存在,则创建一个新的实例
// @ts-ignore ...
this.instance = new this()
}
return this.instance
}
}

7
src/common/readme.md

@ -0,0 +1,7 @@
## event
通用事件处理模块
- main/**/* 处理主进程的模块
- main/command.ts 会通过ioc收集,进入依赖管理中
- 其他 处理渲染进程的模块

14
src/main/App.ts

@ -10,9 +10,7 @@ import BaseClass from "./base/base"
import IOC from "./_ioc"
import DB from "./modules/db"
import Zephyr from "./modules/zephyr"
import Updater from "./modules/updater"
import { crashHandler } from "logger/crash-handler"
import { eventbus } from "./event"
protocol.registerSchemesAsPrivileged([
// {
@ -44,12 +42,6 @@ protocol.registerSchemesAsPrivileged([
@injectable()
class App extends BaseClass {
static events = {
AppInit: "App.init",
AppReady: "App.ready",
}
destroy() {
this._IOC.destroy()
// 这里是应用正常退出, 可以检测应用是不是非正常退出,比如应用启动时记录一个启动时间并删除上一次结束时间和开始时间,结束时记录一个结束时间,
@ -63,7 +55,6 @@ class App extends BaseClass {
@inject(DB) private _DB: DB,
@inject(WindowManager) private _WindowManager: WindowManager,
@inject(Zephyr) private _Zephyr: Zephyr,
@inject(Updater) private _Updater: Updater,
) {
super()
}
@ -73,10 +64,8 @@ class App extends BaseClass {
// 主进程中添加如下代码即可
app.commandLine.appendSwitch("wm-window-animations-disabled")
// 开启硬件加速
app.disableHardwareAcceleration();
eventbus.emit(App.events.AppInit)
app.disableHardwareAcceleration()
crashHandler.init()
this._Updater.init()
this._DB.init()
this._Command.init()
this._WindowManager.init()
@ -91,7 +80,6 @@ class App extends BaseClass {
color: "#F8F8F8",
symbolColor: "#000000",
})
eventbus.emit(App.events.AppReady)
})
app.on("will-quit", () => {
this.destroy()

3
src/main/base/base.ts

@ -49,10 +49,7 @@
// export { BaseClass }
// export default BaseClass
import EventEmitter from "node:events"
abstract class BaseClass {
public _events = new EventEmitter()
abstract init(...argus: any[])
abstract destroy()
}

60
src/main/debug.ts

@ -0,0 +1,60 @@
import debug from "debug"
import { app } from "electron"
import path from "node:path"
import logger from "logger/main"
import * as rfs from "rotating-file-stream"
import fs from "fs"
// 配置根目录
const logsPath = app.getPath("logs")
logger.debug(`日志地址:${logsPath}`)
const LOG_ROOT = path.join(logsPath)
// 缓存当前应用启动的日志文件流
let currentLogStream: rfs.RotatingFileStream | null = null
// 生成当前启动时的日志文件名
const getLogFileName = () => {
const now = new Date()
const timestamp = now.toISOString().replace(/[:.]/g, "-")
return `app-${timestamp}.log`
}
// 覆盖 debug.log 方法
const originalLog = debug.log
debug.log = function (...args) {
// 保留原始控制台输出
originalLog.apply(this, args)
// 确保日志目录存在
if (!fs.existsSync(LOG_ROOT)) {
fs.mkdirSync(LOG_ROOT, { recursive: true })
}
// 延迟初始化日志流,直到第一次写入
if (!currentLogStream) {
const logFileName = getLogFileName()
currentLogStream = rfs.createStream(logFileName, {
path: LOG_ROOT,
size: "10M", // 单个文件最大 10MB
rotate: 10, // 保留最近 10 个文件
})
}
// @ts-ignore 获取当前命名空间
const namespace = this.namespace || "unknown"
// 写入日志(添加时间戳和命名空间)
const timestamp = new Date().toISOString()
const message = args.join(" ")
currentLogStream.write(`[${timestamp}] [${namespace}] ${message}\n`)
}
app.on("before-quit", () => {
if (currentLogStream) {
currentLogStream.end()
currentLogStream.destroy()
currentLogStream = null
}
})

66
src/main/index.ts

@ -1,73 +1,11 @@
import "reflect-metadata"
import "./debug"
import "setting/main"
import "logger/main"
import "logger/main-error"
import "setting/main"
import { _ioc } from "main/_ioc"
import { App } from "main/App"
import debug from "debug"
import fs from "fs"
import path from "path"
import * as rfs from "rotating-file-stream"
import { app } from "electron"
// 配置根目录
const logsPath = app.getPath("logs")
console.log(`日志地址:${logsPath}`)
const LOG_ROOT = path.join(logsPath)
// 缓存当前应用启动的日志文件流
let currentLogStream: rfs.RotatingFileStream | null = null
// 生成当前启动时的日志文件名
const getLogFileName = () => {
const now = new Date()
const timestamp = now.toISOString().replace(/[:.]/g, '-')
return `app-${timestamp}.log`
}
// 覆盖 debug.log 方法
const originalLog = debug.log
debug.log = function (...args) {
// 保留原始控制台输出
originalLog.apply(this, args)
// 确保日志目录存在
if (!fs.existsSync(LOG_ROOT)) {
fs.mkdirSync(LOG_ROOT, { recursive: true })
}
// 延迟初始化日志流,直到第一次写入
if (!currentLogStream) {
const logFileName = getLogFileName()
currentLogStream = rfs.createStream(logFileName, {
path: LOG_ROOT,
size: "10M", // 单个文件最大 10MB
rotate: 10, // 保留最近 10 个文件
})
}
// 获取当前命名空间
// @ts-ignore
const namespace = this.namespace || 'unknown'
// 写入日志(添加时间戳和命名空间)
const timestamp = new Date().toISOString()
const message = args.join(" ")
currentLogStream.write(`[${timestamp}] [${namespace}] ${message}\n`)
}
const curApp = _ioc.get(App)
curApp.init()
const _debug = debug("app:app")
app.on("before-quit", () => {
_debug("应用关闭")
if (currentLogStream) {
currentLogStream.end()
currentLogStream.destroy()
currentLogStream = null
}
})

3
src/main/modules/_ioc.ts

@ -5,11 +5,9 @@ import { WindowManager } from "./window-manager"
import { Tabs } from "./tabs"
import Commands from "./commands"
import Zephyr from "./zephyr"
import Updater from "./updater"
const modules = new ContainerModule(bind => {
bind(Zephyr).toSelf().inSingletonScope()
bind(Updater).toSelf().inSingletonScope()
bind(Api).toSelf().inSingletonScope()
bind(WindowManager).toSelf().inSingletonScope()
bind(Commands).toSelf().inSingletonScope()
@ -21,7 +19,6 @@ async function destroyAllModules(ioc: Container) {
await Promise.all([
ioc.get(WindowManager).destroy(),
ioc.get(Commands).destroy(),
ioc.get(Updater).destroy(),
ioc.get(Zephyr).destroy(),
ioc.get(Tabs).destroy(),
ioc.get(Api).destroy(),

6
src/main/modules/db/index.ts

@ -3,14 +3,14 @@ import Setting from "setting/main"
import { CustomAdapter, CustomLow } from "./custom"
import path from "node:path"
import BaseClass from "main/base/base"
import _debug from "debug"
import _logger from "logger/main"
const debug = _debug("app:db")
const logger = _logger.createNamespace("db")
@injectable()
class DB extends BaseClass {
destroy() {
debug(`DB destroy`)
logger.debug(`DB destroy`)
}
Modules: Record<string, CustomLow<any>> = {}

8
src/main/modules/tabs/Tab.ts

@ -5,8 +5,9 @@ import _debug from "debug"
// import { Layout } from "./Constant"
import FuckHTML from "@res/fuck.html?asset"
import { fileURLToPath, pathToFileURL } from "node:url"
import EventEmitter from "node:events"
const debug = _debug("app:tab")
const debug = _debug("tab")
interface IOption {
url: string
@ -24,6 +25,7 @@ class Tab extends BaseClass {
init() {
// TODO
}
public events = new EventEmitter()
public url: string = ""
public showUrl: string = ""
public title: string = ""
@ -55,10 +57,6 @@ class Tab extends BaseClass {
return this.active
}
get events() {
return this._events
}
constructor(options = {}, window: BrowserWindow, curRect?: IRect) {
super()
this.listenResize = this.listenResize.bind(this)

2
src/main/modules/tabs/index.ts

@ -11,7 +11,7 @@ interface IRect {
height: number
}
const debug = _debug("app:tabs")
const debug = _debug("tabs")
class Tabs extends BaseClass {
destroy() {

3
src/main/modules/window-manager/index.ts

@ -3,7 +3,6 @@ import { cloneDeep, merge } from "lodash"
import { defaultConfig, defaultWindowConfig, getWindowsMap, IConfig, Param } from "./windowsMap"
import { optimizer } from "@electron-toolkit/utils"
import BaseClass from "main/base/base"
import _debug from "debug"
import _logger from "logger/main"
const logger = _logger.createNamespace("modlue:window-manager") // _debug("app:window-manager")
@ -66,7 +65,7 @@ export default class WindowManager extends BaseClass {
}
createWindow(name: string, opts?: Partial<IConfig>){
let info = opts as Param
const info = opts as Param
info.name = name
if (!info.ignoreEmptyUrl && !info.url) {
dialog.showErrorBox("错误", name + "窗口未提供url")

35
src/main/modules/zephyr/index.ts

@ -1,12 +1,12 @@
import { session, net } from "electron"
import { injectable } from "inversify"
import BaseClass from "main/base/base"
import _debug from "debug"
import _logger from "logger/main"
import fs from "fs"
import path from "path"
import { app } from "electron"
const debug = _debug("app:zephyr")
const logger = _logger.createNamespace("zephyr")
/**
* Zephyr - 访
@ -108,7 +108,7 @@ class Zephyr extends BaseClass {
super()
this.interceptHandlerZephyr = this.interceptHandlerZephyr.bind(this)
this.initLogFile()
debug("zephyr init")
logger.debug("zephyr init")
}
private async initLogFile() {
@ -135,7 +135,7 @@ class Zephyr extends BaseClass {
const count = this.rateLimiter.get(filePath) || 0
if (count >= this.MAX_REQUESTS) {
debug("访问频率超限:", filePath)
logger.debug("访问频率超限:", filePath)
return true
}
@ -164,7 +164,7 @@ class Zephyr extends BaseClass {
try {
await fs.promises.appendFile(this.LOG_FILE, JSON.stringify(logEntry) + "\n", "utf8")
} catch (error) {
debug("写入审计日志失败:", error)
logger.debug("写入审计日志失败:", error)
}
}
@ -197,18 +197,17 @@ class Zephyr extends BaseClass {
ses.protocol.unhandle("zephyr")
this.fileLocks.clear()
this.rateLimiter.clear()
debug("zephyr destroyed")
}
init(partition?: string) {
const ses = partition ? session.fromPartition(partition) : session.defaultSession
ses.protocol.handle("zephyr", this.interceptHandlerZephyr)
debug("zephyr initialized with partition:", partition)
logger.debug("zephyr initialized with partition:", partition)
}
setAllowedPaths(config: Partial<typeof this.pathConfig>) {
Object.assign(this.pathConfig, config)
debug("Updated allowed paths:", this.pathConfig)
logger.debug("Updated allowed paths:", this.pathConfig)
}
private isValidPath(filePath: string): boolean {
@ -233,32 +232,32 @@ class Zephyr extends BaseClass {
try {
// 1. 基本路径检查
if (!this.isValidPath(filePath)) {
debug("不安全的路径字符:", filePath)
logger.debug("不安全的路径字符:", filePath)
return false
}
// 2. 检查是否包含 .. 路径
if (filePath.includes("..")) {
debug("检测到路径遍历尝试")
logger.debug("检测到路径遍历尝试")
return false
}
// 3. 检查符号链接
if (await this.isSymlink(filePath)) {
debug("不允许访问符号链接")
logger.debug("不允许访问符号链接")
return false
}
// 4. 检查文件大小
if (!(await this.checkFileSize(filePath))) {
debug("文件超出大小限制")
logger.debug("文件超出大小限制")
return false
}
// 5. 文件类型检查
const ext = path.extname(filePath).toLowerCase()
if (!this.ALLOWED_EXTENSIONS.includes(ext)) {
debug("不允许的文件类型:", ext)
logger.debug("不允许的文件类型:", ext)
return false
}
@ -274,7 +273,7 @@ class Zephyr extends BaseClass {
})
if (!isInAllowedPath) {
debug("路径不在允许范围内")
logger.debug("路径不在允许范围内")
return false
}
@ -294,7 +293,7 @@ class Zephyr extends BaseClass {
return true
} catch (error: any) {
await this.logAccess(operation, filePath, false, error.message)
debug("路径安全检查错误:", error)
logger.debug("路径安全检查错误:", error)
return false
}
}
@ -314,7 +313,7 @@ class Zephyr extends BaseClass {
}
if (!(await this.isPathSafe(filePath, operation))) {
debug("访问被拒绝:", filePath)
logger.debug("访问被拒绝:", filePath)
return new Response("Access Denied", { status: 403 })
}
@ -328,7 +327,7 @@ class Zephyr extends BaseClass {
return new Response("Operation not supported", { status: 400 })
}
} catch (error) {
debug("处理请求错误:", error)
logger.debug("处理请求错误:", error)
return new Response("Internal Server Error", { status: 500 })
}
}
@ -420,7 +419,7 @@ class Zephyr extends BaseClass {
case this.OPERATIONS.WRITE:
return this.pathConfig.write
default:
debug("未知的操作类型:", operation)
logger.debug("未知的操作类型:", operation)
return null
}
}

2
src/main/utils/index.ts

@ -12,7 +12,7 @@ export function getFileUrl(app: string) {
return slash(winURL)
}
export function getPreloadUrl(file){
export function getPreloadUrl(file) {
return join(__dirname, `../preload/${file}.mjs`)
}

10
tsconfig.node.json

@ -13,12 +13,17 @@
"packages/logger/preload.ts",
"packages/logger/preload-error.ts",
"packages/logger/common.ts",
"packages/**/*.main.ts",
"packages/**/main/**/*",
"packages/**/main.ts",
"packages/**/*.common.ts",
"packages/**/common.ts",
"src/common/**/*.main.ts",
"src/common/**/main/**/*",
"src/common/**/main.ts",
"src/common/**/*.common.ts",
"src/common/**/common.ts"
, "src/common/event/_Base.ts" ],
],
"compilerOptions": {
"composite": true,
"emitDecoratorMetadata": true,
@ -59,6 +64,9 @@
"logger/*": [
"packages/logger/*"
],
"base/*": [
"packages/base/*"
],
}
}
}

9
tsconfig.web.json

@ -13,7 +13,8 @@
"src/types/**/*",
"config/**/*",
"./typed-router.d.ts",
"src/common/**/*"
"src/common/**/*",
"packages/**/*",
],
"exclude": [
"packages/locales/main.ts",
@ -21,6 +22,9 @@
"packages/logger/main.ts",
"packages/logger/main-error.ts",
"packages/logger/crash-handler.ts",
"packages/**/main/**/*",
"packages/**/*.main.ts",
"packages/**/main.ts",
"src/common/**/main/**/*",
"src/common/**/*.main.ts",
"src/common/**/main.ts"
@ -60,6 +64,9 @@
"@res/*": [
"resources/*"
],
"base/*": [
"packages/base/*"
],
}
}
}

Loading…
Cancel
Save