diff --git a/main/events.ts b/main/events.ts new file mode 100644 index 0000000..9520d4f --- /dev/null +++ b/main/events.ts @@ -0,0 +1,5 @@ +import { EventEmitter } from "events"; + +const eventEmitter = new EventEmitter(); + +export default eventEmitter; diff --git a/main/index.ts b/main/index.ts index 83dc945..00c17fd 100644 --- a/main/index.ts +++ b/main/index.ts @@ -4,65 +4,26 @@ import path from "path"; import store from "./store"; import { exec, failFn, successFn } from "./utils"; import logger from "./logger"; +import XhrFilter from "./xhrFilter"; +import windowManager from "./window/windowManager"; +import { WindowName } from "./window/variables"; // eslint-disable-next-line global-require if (require("electron-squirrel-startup")) { app.quit(); } +const xhrFilter = new XhrFilter(); + if (!is.development) { global.__bin__ = path .resolve(app.getAppPath(), "../.bin") .replace(/\\/g, "\\\\"); } -const createMainWindow = async () => { - const mainWindow = new BrowserWindow({ - width: 590, - minWidth: 590, - height: 600, - frame: false, - webPreferences: { - nodeIntegration: true, - contextIsolation: false, - enableRemoteModule: true, - }, - }); - await mainWindow.loadURL("http://localhost:3000/main_window/"); - if (is.development) mainWindow.webContents.openDevTools(); - return mainWindow; -}; - -const createBrowserWindow = async () => { - const browserWindow = new BrowserWindow({ - width: 800, - height: 600, - show: false, - frame: false, - webPreferences: { - nodeIntegration: true, - contextIsolation: false, - enableRemoteModule: true, - }, - }); - await browserWindow.loadURL("http://localhost:3000/browser_window/"); - if (is.development) browserWindow.webContents.openDevTools(); - - ipcMain.on("openBrowserWindow", () => { - browserWindow.show(); - }); - - ipcMain.on("closeBrowserWindow", () => { - logger.info("closeBrowserWindow"); - browserWindow.hide(); - }); - - return browserWindow; -}; - const init = async () => { - const mainWindow = await createMainWindow(); - const browserWindow = await createBrowserWindow(); + const mainWindow = await windowManager.create(WindowName.MAIN_WINDOW); + const browserWindow = await windowManager.create(WindowName.BROWSER_WINDOW); const partition = "persist:webview"; const ses = session.fromPartition(partition); @@ -78,7 +39,7 @@ const init = async () => { view.setBounds({ x: 0, y: 0, height: 0, width: 0 }); const { webContents } = view; - // if (is.development) webContents.openDevTools(); + if (is.development) webContents.openDevTools(); webContents.on("dom-ready", () => { webContents.on("new-window", async (event, url) => { @@ -87,28 +48,8 @@ const init = async () => { }); }); - ses.webRequest.onBeforeSendHeaders( - { urls: ["*://*/*"] }, - (details, callback) => { - const m3u8Reg = /\.m3u8$/; - const tsReg = /\.ts$/; - let cancel = false; - const myURL = new URL(details.url); - if (m3u8Reg.test(myURL.pathname)) { - logger.info("在窗口中捕获 m3u8 链接: ", details.url); - mainWindow.webContents.send("m3u8", { - title: webContents.getTitle(), - requestDetails: details, - }); - } else if (tsReg.test(myURL.pathname)) { - cancel = true; - } - callback({ - cancel, - requestHeaders: details.requestHeaders, - }); - } - ); + const filter = { urls: ["*://*/*"] }; + ses.webRequest.onBeforeSendHeaders(filter, xhrFilter.beforeSendHeaders); }; app.on("window-all-closed", () => { diff --git a/main/window/variables.ts b/main/window/variables.ts new file mode 100644 index 0000000..2803737 --- /dev/null +++ b/main/window/variables.ts @@ -0,0 +1,4 @@ +export enum WindowName { + MAIN_WINDOW = "MAIN_WINDOW", + BROWSER_WINDOW = "BROWSER_WINDOW", +} diff --git a/main/window/windowList.ts b/main/window/windowList.ts new file mode 100644 index 0000000..a4545e3 --- /dev/null +++ b/main/window/windowList.ts @@ -0,0 +1,62 @@ +import { IWindowListItem } from "../../types/main"; +import { is } from "electron-util"; +import { ipcMain } from "electron"; +import logger from "../logger"; +import { WindowName } from "./variables"; + +const windowList = new Map(); + +windowList.set(WindowName.MAIN_WINDOW, { + url: is.development + ? "http://localhost:3000/main_window/" + : "test://123132123123123", + options() { + return { + width: 590, + minWidth: 590, + height: 600, + frame: false, + webPreferences: { + nodeIntegration: true, + contextIsolation: false, + enableRemoteModule: true, + }, + }; + }, + async callback(window) { + if (is.development) window.webContents.openDevTools(); + }, +}); + +windowList.set(WindowName.BROWSER_WINDOW, { + url: is.development + ? "http://localhost:3000/browser_window/" + : "test://123132123123123", + options() { + return { + width: 800, + height: 600, + show: false, + frame: false, + webPreferences: { + nodeIntegration: true, + contextIsolation: false, + enableRemoteModule: true, + }, + }; + }, + async callback(window) { + if (is.development) window.webContents.openDevTools(); + + ipcMain.on("openBrowserWindow", () => { + window.show(); + }); + + ipcMain.on("closeBrowserWindow", () => { + logger.info("closeBrowserWindow"); + window.hide(); + }); + }, +}); + +export default windowList; diff --git a/main/window/windowManager.ts b/main/window/windowManager.ts new file mode 100644 index 0000000..700665d --- /dev/null +++ b/main/window/windowManager.ts @@ -0,0 +1,41 @@ +import { BrowserWindow } from "electron"; +import windowList from "./windowList"; +import { IWindowListItem, IWindowManager } from "../../types/main"; +import { WindowName } from "./variables"; + +class WindowManager implements IWindowManager { + private windowMap: Map = new Map(); + private windowIdMap: Map = new Map(); + + async create(name: WindowName) { + const windowConfig: IWindowListItem = windowList.get(name)!; + const window = new BrowserWindow(windowConfig.options()); + const id = window.id; + this.windowMap.set(name, window); + this.windowIdMap.set(window.id, name); + await window.loadURL(windowConfig.url); + await windowConfig.callback(window, this); + window.on("close", () => { + this.deleteById(id); + }); + return window; + } + + get(name: WindowName) { + return this.windowMap.get(name)!; + } + + has(name: WindowName) { + return this.windowMap.has(name); + } + + deleteById = (id: number) => { + const name = this.windowIdMap.get(id); + if (name) { + this.windowMap.delete(name); + this.windowIdMap.delete(id); + } + }; +} + +export default new WindowManager(); diff --git a/main/xhrFilter.ts b/main/xhrFilter.ts new file mode 100644 index 0000000..4129e8a --- /dev/null +++ b/main/xhrFilter.ts @@ -0,0 +1,54 @@ +import logger from "./logger"; +import windowManager from "./window/windowManager"; +import { WindowName } from "./window/variables"; + +class XhrFilter { + // 当请求即将发生时 + beforeRequest() {} + // 一旦请求头可用,在发送 HTTP 请求之前,listener 将以 + // listener(details, callback) 的形式被调用。 + // 这可能发生在对服务器进行 TCP 连接之后 + // ,但在发送任何HTTP数据之前。 + beforeSendHeaders( + details: Electron.OnBeforeSendHeadersListenerDetails, + callback: (beforeSendResponse: Electron.BeforeSendResponse) => void + ) { + const m3u8Reg = /\.m3u8$/; + const tsReg = /\.ts$/; + let cancel = false; + const myURL = new URL(details.url); + if (m3u8Reg.test(myURL.pathname)) { + logger.info("在窗口中捕获 m3u8 链接: ", details.url); + const { webContents } = windowManager.get(WindowName.MAIN_WINDOW) ?? {}; + webContents.send("m3u8", { + title: webContents.getTitle(), + requestDetails: details, + }); + } else if (tsReg.test(myURL.pathname)) { + cancel = true; + } + callback({ + cancel, + requestHeaders: details.requestHeaders, + }); + } + // 在请求发送到服务器之前,listener将以listener(details)的形式被调用 + // ,在该侦听器被出发前,上一个对 onBeforeSendHeaders + // 响应的修改是可见的。 + sendHeaders() {} + // 当HTTP请求接收到报头后,会通过调用 listener(details, callback) + // 方法来触发listener。 + // The callback has to be called with a response object. + headersReceived() {} + // 当收到响应体的第一个字节时, 将以 listener(details) 的形式来调用 listener。 + // 对于 HTTP 请求而言,这意味着此时 HTTP 状态行和回应头已经可以读取了。 + responseStarted() {} + // 当服务器的初始重定向即将发生时,将以 listener(details)的方式调用listener。 + beforeRedirect() {} + // 当请求完成时,将以 listener(details)的方式调用listener。 + completed() {} + // 当发生错误时,将以 listener(details)的方式调用listener。 + errorOccurred() {} +} + +export default XhrFilter; diff --git a/package.json b/package.json index 16cc7d3..efa309a 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "eslint-plugin-react": "7.21.5", "eslint-plugin-react-hooks": "1.7.0", "glob": "^7.1.6", + "mime": "^2.5.2", "prettier": "2.2.1", "sass": "^1.32.8", "semver": "^7.3.4", diff --git a/types/main.d.ts b/types/main.d.ts index 2ba2376..359ade1 100644 --- a/types/main.d.ts +++ b/types/main.d.ts @@ -1,3 +1,5 @@ +import { BrowserWindow } from "electron"; + declare global { namespace NodeJS { interface Global { @@ -8,4 +10,22 @@ declare global { declare var __bin__: string; -export {}; +declare enum WindowName { + MAIN_WINDOW = "MAIN_WINDOW", + BROWSER_WINDOW = "BROWSER_WINDOW", +} + +declare interface IWindowManager { + create: (name: WindowName) => Promise; + get: (name: WindowName) => BrowserWindow | null; + has: (name: WindowName) => boolean; + deleteById: (id: number) => void; +} + +declare interface IWindowListItem { + url: string; + options: () => Electron.BrowserWindowConstructorOptions; + callback: (window: BrowserWindow, windowManager: IWindowManager) => void; +} + +export { IWindowManager, IWindowListItem };