diff --git a/src/ipc/ipc-event.ts b/src/ipc/ipc-event.ts index 0cfa5ead..4447e0f1 100644 --- a/src/ipc/ipc-event.ts +++ b/src/ipc/ipc-event.ts @@ -1,73 +1,125 @@ -export const GET_CONFIG = 'get-config' -export const SAVE_CONFIG = 'OVERLAY->MAIN::save-config' +import type { Config } from '@/ipc/types' -export const PRICE_CHECK_HIDE = 'OVERLAY->MAIN::price-check-hide' +export type IpcEvent = + IpcGetConfig | + IpcSaveConfig | + IpcPriceCheckHide | + IpcUpdateInfo | + IpcFocusChange | + IpcDPRChange | + IpcPriceCheck | + IpcPriceCheckCanceled | + IpcItemCheck | + IpcStashSearch | + IpcOverlayReady | + IpcCloseOverlay | + IpcShowBrowser | + IpcHideBrowser | + IpcVisibility | + IpcOpenSystemBrowser | + IpcOpenWiki | + IpcOpenCraftOfExile | + IpcImportFile | + IpcToggleDelveGrid | + IpcClientLog -export const UPDATE_AVAILABLE = 'update-available' -export interface IpcUpdateInfo { - auto: boolean - version: string -} - -export const FOCUS_CHANGE = 'MAIN->OVERLAY::focus-change' -export interface IpcFocusChange { - game: boolean - overlay: boolean - usingHotkey: boolean -} - -export const DPR_CHANGE = 'OVERLAY->MAIN::devicePixelRatio-change' - -export const PRICE_CHECK = 'MAIN->OVERLAY::price-check' -export const PRICE_CHECK_CANCELED = 'MAIN->OVERLAY::price-check-canceled' -export interface IpcPriceCheck { - clipboard: string - position: { x: number, y: number } - lockedMode: boolean -} - -export const ITEM_CHECK = 'MAIN->OVERLAY::item-check' -export interface IpcItemCheck { - clipboard: string - position: { x: number, y: number } -} - -export const STASH_SEARCH = 'OVERLAY->MAIN::stash-search' -export interface IpcStashSearch { - text: string -} - -export const OVERLAY_READY = 'OVERLAY->MAIN::ready' - -export const CLOSE_OVERLAY = 'OVERLAY->MAIN::close-overlay' - -export const SHOW_BROWSER = 'OVERLAY->MAIN::show-browser' -export interface IpcShowBrowser { - url?: string -} - -export const HIDE_BROWSER = 'OVERLAY->MAIN::hide-browser' -export interface IpcHideBrowser { - close?: boolean -} - -export const VISIBILITY = 'MAIN->OVERLAY::visibility' -export interface IpcVisibility { - isVisible: boolean -} - -export const OPEN_SYSTEM_BROWSER = 'OVERLAY->MAIN::system-browser' -export interface IpcOpenSystemBrowser { - url: string -} - -export const OPEN_WIKI = 'MAIN->OVERLAY::open-wiki' -export const OPEN_COE = 'MAIN->OVERLAY::open-craft-of-exile' -export const IMPORT_FILE = 'OVERLAY->MAIN::import-file' - -export const TOGGLE_DELVE_GRID = 'MAIN->OVERLAY::delve-grid' - -export const CLIENT_LOG_UPDATE = 'MAIN->OVERLAY::client-log' -export interface IpcClientLog { - lines: string[] +export type IpcEventPayload = + T extends { name: Name } ? T['payload'] : never + +export type IpcGetConfig = + Event<'OVERLAY->MAIN::get-config', Config> + +export type IpcSaveConfig = + Event<'OVERLAY->MAIN::save-config', Config> + +export type IpcPriceCheckHide = + Event<'OVERLAY->MAIN::price-check-hide'> + +export type IpcUpdateInfo = + Event<'MAIN->OVERLAY::update-available', { + auto: boolean + version: string + }> + +export type IpcFocusChange = + Event<'MAIN->OVERLAY::focus-change', { + game: boolean + overlay: boolean + usingHotkey: boolean + }> + +export type IpcDPRChange = + Event<'OVERLAY->MAIN::devicePixelRatio-change', number> + +export type IpcPriceCheck = + Event<'MAIN->OVERLAY::price-check', { + clipboard: string + position: { x: number, y: number } + lockedMode: boolean + }> + +export type IpcPriceCheckCanceled = + Event<'MAIN->OVERLAY::price-check-canceled'> + +export type IpcItemCheck = + Event<'MAIN->OVERLAY::item-check', { + clipboard: string + position: { x: number, y: number } + }> + +export type IpcStashSearch = + Event<'OVERLAY->MAIN::stash-search', { + text: string + }> + +export type IpcOverlayReady = + Event<'OVERLAY->MAIN::ready'> + +export type IpcCloseOverlay = + Event<'OVERLAY->MAIN::close-overlay'> + +export type IpcShowBrowser = + Event<'OVERLAY->MAIN::show-browser', { + url?: string + }> + +export type IpcHideBrowser = + Event<'OVERLAY->MAIN::hide-browser', { + close?: boolean + }> + +export type IpcVisibility = + Event<'MAIN->OVERLAY::visibility', { + isVisible: boolean + }> + +export type IpcOpenSystemBrowser = + Event<'OVERLAY->MAIN::system-browser', string> + +export type IpcOpenWiki = + Event<'MAIN->OVERLAY::open-wiki', { + clipboard: string + position: { x: number, y: number } + }> + +export type IpcOpenCraftOfExile = + Event<'MAIN->OVERLAY::open-craft-of-exile', { + clipboard: string + position: { x: number, y: number } + }> + +export type IpcImportFile = + Event<'OVERLAY->MAIN::import-file', string> + +export type IpcToggleDelveGrid = + Event<'MAIN->OVERLAY::delve-grid'> + +export type IpcClientLog = + Event<'MAIN->OVERLAY::client-log', { + lines: string[] + }> + +interface Event { + name: TName + payload: TPayload } diff --git a/src/ipc/main-process-bindings.ts b/src/ipc/main-process-bindings.ts deleted file mode 100644 index 657711ed..00000000 --- a/src/ipc/main-process-bindings.ts +++ /dev/null @@ -1,158 +0,0 @@ -import { Renderer } from 'electron' -import * as ipcEvent from '@/ipc/ipc-event' -import { Config, defaultConfig } from '@/ipc/types' - -let electron: typeof Renderer | undefined -try { - electron = require('electron') -} catch {} - -class MainProcessBinding extends EventTarget { - constructor () { - super() - - if (electron) { - electron.ipcRenderer.on(ipcEvent.PRICE_CHECK, (e, data) => { - this.selfEmitPriceCheck(data) - }) - - electron.ipcRenderer.on(ipcEvent.ITEM_CHECK, (e, data) => { - this.dispatchEvent(new CustomEvent(ipcEvent.ITEM_CHECK, { - detail: data - })) - }) - - electron.ipcRenderer.on(ipcEvent.SAVE_CONFIG, (e, cfg) => { - this.dispatchEvent(new CustomEvent(ipcEvent.SAVE_CONFIG, { - detail: cfg - })) - }) - - electron.ipcRenderer.on(ipcEvent.FOCUS_CHANGE, (e, data) => { - this.dispatchEvent(new CustomEvent(ipcEvent.FOCUS_CHANGE, { detail: data })) - }) - - electron.ipcRenderer.on(ipcEvent.PRICE_CHECK_CANCELED, () => { - this.dispatchEvent(new CustomEvent(ipcEvent.PRICE_CHECK_CANCELED)) - }) - - electron.ipcRenderer.on(ipcEvent.UPDATE_AVAILABLE, (e, updateInfo) => { - this.dispatchEvent(new CustomEvent(ipcEvent.UPDATE_AVAILABLE, { - detail: updateInfo - })) - }) - - electron.ipcRenderer.on(ipcEvent.VISIBILITY, (e, detail) => { - this.dispatchEvent(new CustomEvent(ipcEvent.VISIBILITY, { detail })) - }) - - electron.ipcRenderer.on(ipcEvent.OPEN_WIKI, (e, detail) => { - this.dispatchEvent(new CustomEvent(ipcEvent.OPEN_WIKI, { detail })) - }) - - electron.ipcRenderer.on(ipcEvent.OPEN_COE, (e, detail) => { - this.dispatchEvent(new CustomEvent(ipcEvent.OPEN_COE, { detail })) - }) - - electron.ipcRenderer.on(ipcEvent.TOGGLE_DELVE_GRID, (e) => { - this.dispatchEvent(new CustomEvent(ipcEvent.TOGGLE_DELVE_GRID)) - }) - - electron.ipcRenderer.on(ipcEvent.CLIENT_LOG_UPDATE, (e, data: ipcEvent.IpcClientLog) => { - data.lines.forEach(line => { - this.dispatchEvent(new CustomEvent(ipcEvent.CLIENT_LOG_UPDATE, { - detail: line - })) - }) - }) - } - } - - selfEmitPriceCheck (e: ipcEvent.IpcPriceCheck) { - this.dispatchEvent(new CustomEvent(ipcEvent.PRICE_CHECK, { - detail: e - })) - } - - readyReceiveEvents () { - if (electron) { - electron.ipcRenderer.send(ipcEvent.OVERLAY_READY) - } - } - - dprChanged (dpr: number) { - if (electron) { - electron.ipcRenderer.send(ipcEvent.DPR_CHANGE, dpr) - } - } - - closeOverlay () { - if (electron) { - electron.ipcRenderer.send(ipcEvent.CLOSE_OVERLAY) - } - } - - priceCheckWidgetIsHidden () { - if (electron) { - electron.ipcRenderer.send(ipcEvent.PRICE_CHECK_HIDE) - } - } - - getConfig (): Config { - if (electron) { - return electron.ipcRenderer.sendSync(ipcEvent.GET_CONFIG) - } else { - return defaultConfig() - } - } - - openSystemBrowser (url: string) { - if (electron) { - electron.ipcRenderer.send(ipcEvent.OPEN_SYSTEM_BROWSER, { url } as ipcEvent.IpcOpenSystemBrowser) - } - } - - openAppBrowser (opts: ipcEvent.IpcShowBrowser) { - if (electron) { - electron.ipcRenderer.send(ipcEvent.SHOW_BROWSER, opts) - } else if (opts.url) { - window.open(opts.url) - } - } - - hideAppBrowser (opts: ipcEvent.IpcHideBrowser) { - if (electron) { - electron.ipcRenderer.send(ipcEvent.HIDE_BROWSER, opts) - } - } - - stashSearch (text: string) { - if (electron) { - electron.ipcRenderer.send(ipcEvent.STASH_SEARCH, { text }) - } - } - - saveConfig (config: Config) { - if (electron) { - electron.ipcRenderer.send(ipcEvent.SAVE_CONFIG, config) - } - } - - importFile (filePath: string) { - if (electron) { - return electron.ipcRenderer.sendSync(ipcEvent.IMPORT_FILE, filePath) - } - } - - get CORS () { - return (!electron) - ? 'https://apt-cors.snos.workers.dev/?' - : '' - } - - get isElectron () { - return (electron != null) - } -} - -export const MainProcess = new MainProcessBinding() diff --git a/src/main/LogWatcher.ts b/src/main/LogWatcher.ts index 656b766d..8be87c6d 100644 --- a/src/main/LogWatcher.ts +++ b/src/main/LogWatcher.ts @@ -1,6 +1,5 @@ import { promises as fs, watchFile, unwatchFile } from 'fs' -import { overlayWindow } from './overlay-window' -import * as ipc from '@/ipc/ipc-event' +import { overlaySendEvent } from './overlay-window' import { config } from './config' import { logger } from './logger' @@ -76,7 +75,10 @@ export class LogWatcher { if (bytesRead) { const str = this.readBuff.toString('utf8', 0, bytesRead) const lines = str.split('\n').map(line => line.trim()).filter(line => line.length) - overlayWindow!.webContents.send(ipc.CLIENT_LOG_UPDATE, { lines } as ipc.IpcClientLog) + overlaySendEvent({ + name: 'MAIN->OVERLAY::client-log', + payload: { lines } + }) } if (bytesRead) { diff --git a/src/main/alt-visibility.ts b/src/main/alt-visibility.ts index 0bbed345..ffb81668 100644 --- a/src/main/alt-visibility.ts +++ b/src/main/alt-visibility.ts @@ -1,5 +1,4 @@ -import { overlayWindow, isInteractable } from './overlay-window' -import { VISIBILITY, IpcVisibility } from '@/ipc/ipc-event' +import { isInteractable, overlaySendEvent } from './overlay-window' import { uIOhook, UiohookKey } from 'uiohook-napi' import { logger } from './logger' @@ -34,7 +33,10 @@ function makeVisible () { } else { logger.debug('Making visible again', { source: 'alt-visibility' }) isOverlayVisible = true - overlayWindow!.webContents.send(VISIBILITY, { isVisible: isOverlayVisible } as IpcVisibility) + overlaySendEvent({ + name: 'MAIN->OVERLAY::visibility', + payload: { isVisible: isOverlayVisible } + }) } } @@ -46,6 +48,9 @@ function makeInvisible () { logger.debug('Delay passed, overlay is invisible', { source: 'alt-visibility' }) timerId = undefined isOverlayVisible = false - overlayWindow!.webContents.send(VISIBILITY, { isVisible: isOverlayVisible } as IpcVisibility) + overlaySendEvent({ + name: 'MAIN->OVERLAY::visibility', + payload: { isVisible: isOverlayVisible } + }) }, isInteractable ? 85 : 275) } diff --git a/src/main/app-file-protocol.ts b/src/main/app-file-protocol.ts index 7e313cca..828dfe4c 100644 --- a/src/main/app-file-protocol.ts +++ b/src/main/app-file-protocol.ts @@ -1,9 +1,9 @@ -import { protocol, app, ipcMain } from 'electron' -import { IMPORT_FILE } from '@/ipc/ipc-event' +import { protocol, app } from 'electron' import { URL } from 'url' import path from 'path' import crypto from 'crypto' import fs from 'fs' +import { overlayOnEvent } from './overlay-window' export function createFileProtocol () { protocol.registerFileProtocol('app', (req, resp) => { @@ -16,7 +16,7 @@ export function createFileProtocol () { resp({ path: path.join(app.getPath('userData'), 'apt-data/files', req.url.substr('app-file://'.length)) }) }) - ipcMain.on(IMPORT_FILE, (e, filePath: string) => { + overlayOnEvent('OVERLAY->MAIN::import-file', (e, filePath) => { const file = fs.readFileSync(filePath) const hash = crypto.createHash('md5').update(file).digest('hex') const filename = `${hash}${path.extname(filePath)}` diff --git a/src/main/builtin-browser.ts b/src/main/builtin-browser.ts index 5b157015..16050489 100644 --- a/src/main/builtin-browser.ts +++ b/src/main/builtin-browser.ts @@ -1,6 +1,5 @@ -import { ipcMain, BrowserView, screen, shell } from 'electron' -import * as ipc from '@/ipc/ipc-event' -import { DPR, overlayWindow, handleExtraCommands } from './overlay-window' +import { BrowserView, screen, shell } from 'electron' +import { DPR, overlayWindow, handleExtraCommands, overlayOnEvent } from './overlay-window' import { PoeWindow } from './PoeWindow' import { logger } from './logger' import { config } from './config' @@ -10,7 +9,7 @@ const WIDTH_96DPI = 460 / 16 let browserViewExternal: BrowserView | undefined export function setupBuiltinBrowser () { - ipcMain.on(ipc.SHOW_BROWSER, (e, opts: ipc.IpcShowBrowser) => { + overlayOnEvent('OVERLAY->MAIN::show-browser', (_, opts) => { logger.debug('Show', { source: 'builtin-browser', opts }) if (!browserViewExternal) { browserViewExternal = new BrowserView() @@ -26,8 +25,8 @@ export function setupBuiltinBrowser () { let browserBounds = { x: 0, y: 0, - width: PoeWindow.bounds!.width - Math.floor(WIDTH_96DPI * DPR * config.get('fontSize')), - height: PoeWindow.bounds!.height + width: PoeWindow.bounds.width - Math.floor(WIDTH_96DPI * DPR * config.get('fontSize')), + height: PoeWindow.bounds.height } if (process.platform === 'win32') { browserBounds = screen.screenToDipRect(overlayWindow!, browserBounds) @@ -38,7 +37,7 @@ export function setupBuiltinBrowser () { } }) - ipcMain.on(ipc.HIDE_BROWSER, (e, opts: ipc.IpcHideBrowser) => { + overlayOnEvent('OVERLAY->MAIN::hide-browser', (_, opts) => { logger.debug('Hide', { source: 'builtin-browser', close: opts.close || false }) if (browserViewExternal) { overlayWindow!.removeBrowserView(browserViewExternal) @@ -51,7 +50,7 @@ export function setupBuiltinBrowser () { } }) - ipcMain.on(ipc.OPEN_SYSTEM_BROWSER, (e, opts: ipc.IpcOpenSystemBrowser) => { - shell.openExternal(opts.url) + overlayOnEvent('OVERLAY->MAIN::system-browser', (_, url) => { + shell.openExternal(url) }) } diff --git a/src/main/config.ts b/src/main/config.ts index e4deaef8..ae5898a1 100644 --- a/src/main/config.ts +++ b/src/main/config.ts @@ -1,18 +1,18 @@ import Store from 'electron-store' -import { dialog, ipcMain, app } from 'electron' +import { dialog, app } from 'electron' import isDeepEq from 'fast-deep-equal' import { Config, defaultConfig } from '@/ipc/types' -import { GET_CONFIG, SAVE_CONFIG } from '@/ipc/ipc-event' import { logger } from './logger' import { LogWatcher } from './LogWatcher' import { ItemCheckWidget } from '@/web/overlay/interfaces' import { loadAndCache as loadAndCacheGameCfg } from './game-config' +import { overlayOnEvent } from './overlay-window' export function setupConfigEvents () { - ipcMain.on(GET_CONFIG, (e) => { + overlayOnEvent('OVERLAY->MAIN::get-config', (e) => { e.returnValue = config.store }) - ipcMain.on(SAVE_CONFIG, (e, cfg: Config) => { + overlayOnEvent('OVERLAY->MAIN::save-config', (_, cfg) => { batchUpdateConfig(cfg) }) } diff --git a/src/main/overlay-window.ts b/src/main/overlay-window.ts index e339a716..3d615daa 100644 --- a/src/main/overlay-window.ts +++ b/src/main/overlay-window.ts @@ -1,5 +1,6 @@ import path from 'path' -import { BrowserWindow, ipcMain, dialog, Menu, systemPreferences } from 'electron' +import assert from 'assert' +import { BrowserWindow, ipcMain, dialog, Menu, systemPreferences, IpcMainEvent } from 'electron' import { PoeWindow } from './PoeWindow' import { logger } from './logger' import * as ipc from '@/ipc/ipc-event' @@ -15,6 +16,18 @@ export const overlayReady = new Promise((resolve) => { _resolveOverlayReady = resolve }) +export function overlaySendEvent (event: ipc.IpcEvent) { + assert.ok(overlayWindow) + overlayWindow.webContents.send('named-event', event) +} + +export function overlayOnEvent ( + name: Name, + cb: (e: IpcMainEvent, payload: ipc.IpcEventPayload) => void +) { + ipcMain.on(name, cb) +} + export async function createOverlayWindow () { if (process.platform === 'win32' && !systemPreferences.isAeroGlassEnabled()) { dialog.showErrorBox( @@ -25,9 +38,9 @@ export async function createOverlayWindow () { ) } - ipcMain.once(ipc.OVERLAY_READY, _resolveOverlayReady) - ipcMain.on(ipc.DPR_CHANGE, (_: any, dpr: number) => handleDprChange(dpr)) - ipcMain.on(ipc.CLOSE_OVERLAY, assertPoEActive) + overlayOnEvent('OVERLAY->MAIN::ready', _resolveOverlayReady) + overlayOnEvent('OVERLAY->MAIN::devicePixelRatio-change', (_, dpr) => handleDprChange(dpr)) + overlayOnEvent('OVERLAY->MAIN::close-overlay', assertPoEActive) PoeWindow.on('active-change', handlePoeWindowActiveChange) PoeWindow.onAttach(handleOverlayAttached) @@ -86,11 +99,14 @@ function handlePoeWindowActiveChange (isActive: boolean) { if (isActive && isInteractable) { isInteractable = false } - overlayWindow!.webContents.send(ipc.FOCUS_CHANGE, { - game: isActive, - overlay: isInteractable, - usingHotkey: _isOverlayKeyUsed - } as ipc.IpcFocusChange) + overlaySendEvent({ + name: 'MAIN->OVERLAY::focus-change', + payload: { + game: isActive, + overlay: isInteractable, + usingHotkey: _isOverlayKeyUsed + } + }) _isOverlayKeyUsed = false } diff --git a/src/main/price-check.ts b/src/main/price-check.ts index ea7156a7..19cfe6bb 100644 --- a/src/main/price-check.ts +++ b/src/main/price-check.ts @@ -1,11 +1,10 @@ -import { ipcMain, Rectangle, Point } from 'electron' +import { Rectangle, Point } from 'electron' import { uIOhook } from 'uiohook-napi' import { isPollingClipboard } from './poll-clipboard' import { PoeWindow } from './PoeWindow' -import * as ipc from '@/ipc/ipc-event' import { config } from './config' import { logger } from './logger' -import { overlayWindow, isInteractable, assertOverlayActive, assertPoEActive, DPR } from './overlay-window' +import { isInteractable, assertOverlayActive, assertPoEActive, DPR, overlayOnEvent, overlaySendEvent } from './overlay-window' import type { PriceCheckWidget } from '@/web/overlay/interfaces' const WIDTH_96DPI = 460 / 16 @@ -26,7 +25,10 @@ export function showWidget (opts: { checkPressPosition = opts.pressPosition const isLokedMode = (opts.eventName === 'price-check-locked') - overlayWindow!.webContents.send(ipc.PRICE_CHECK, { clipboard: opts.clipboard, position: checkPressPosition, lockedMode: isLokedMode } as ipc.IpcPriceCheck) + overlaySendEvent({ + name: 'MAIN->OVERLAY::price-check', + payload: { clipboard: opts.clipboard, position: checkPressPosition, lockedMode: isLokedMode } + }) const poeBounds = PoeWindow.bounds activeAreaRect = { @@ -52,7 +54,7 @@ export function lockWindow (syntheticClick = false) { } export function setupShowHide () { - ipcMain.on(ipc.PRICE_CHECK_HIDE, () => { + overlayOnEvent('OVERLAY->MAIN::price-check-hide', () => { if (isPriceCheckShown) { logger.debug('Closing', { source: 'price-check', reason: 'Event from widget' }) isPriceCheckShown = false @@ -68,7 +70,7 @@ export function setupShowHide () { if (distance > (CLOSE_THRESHOLD_96DPI * DPR * config.get('fontSize'))) { logger.debug('Closing', { source: 'price-check', reason: 'Auto-hide on mouse move', distance, threshold: CLOSE_THRESHOLD_96DPI }) - overlayWindow!.webContents.send(ipc.PRICE_CHECK_CANCELED) + overlaySendEvent({ name: 'MAIN->OVERLAY::price-check-canceled', payload: undefined }) isPriceCheckShown = false } } else if (!isMouseInside) { diff --git a/src/main/shortcuts.ts b/src/main/shortcuts.ts index 5ff8f5f8..43d1adad 100644 --- a/src/main/shortcuts.ts +++ b/src/main/shortcuts.ts @@ -1,4 +1,4 @@ -import { screen, globalShortcut, ipcMain } from 'electron' +import { screen, globalShortcut } from 'electron' import robotjs from 'robotjs' import { uIOhook, UiohookKey, UiohookWheelEvent } from 'uiohook-napi' import { pollClipboard } from './poll-clipboard' @@ -7,7 +7,7 @@ import { isModKey, KeyToElectron, mergeTwoHotkeys } from '@/ipc/KeyToCode' import { config } from './config' import { PoeWindow } from './PoeWindow' import { logger } from './logger' -import { toggleOverlayState, overlayWindow, assertOverlayActive, assertPoEActive } from './overlay-window' +import { toggleOverlayState, assertOverlayActive, assertPoEActive, overlayOnEvent, overlaySendEvent } from './overlay-window' import * as ipc from '@/ipc/ipc-event' import { typeInChat } from './game-chat' import { gameConfig } from './game-config' @@ -20,11 +20,17 @@ export interface ShortcutAction { keepModKeys?: true action: { type: 'copy-item' - eventName: string + eventName: ( + ipc.IpcOpenWiki['name'] | + ipc.IpcOpenCraftOfExile['name'] | + ipc.IpcItemCheck['name'] | + 'price-check-quick' | + 'price-check-locked' + ) focusOverlay?: boolean } | { type: 'trigger-event' - eventName: string + eventName: ipc.IpcToggleDelveGrid['name'] } | { type: 'toggle-overlay' } | { @@ -61,25 +67,25 @@ function shortcutsFromConfig () { if (config.get('wikiKey')) { actions.push({ shortcut: config.get('wikiKey')!, - action: { type: 'copy-item', eventName: ipc.OPEN_WIKI } + action: { type: 'copy-item', eventName: 'MAIN->OVERLAY::open-wiki' } }) } if (config.get('craftOfExileKey')) { actions.push({ shortcut: config.get('craftOfExileKey')!, - action: { type: 'copy-item', eventName: ipc.OPEN_COE } + action: { type: 'copy-item', eventName: 'MAIN->OVERLAY::open-craft-of-exile' } }) } if (config.get('itemCheckKey')) { actions.push({ shortcut: config.get('itemCheckKey')!, - action: { type: 'copy-item', eventName: ipc.ITEM_CHECK, focusOverlay: true } + action: { type: 'copy-item', eventName: 'MAIN->OVERLAY::item-check', focusOverlay: true } }) } if (config.get('delveGridKey')) { actions.push({ shortcut: config.get('delveGridKey')!, - action: { type: 'trigger-event', eventName: ipc.TOGGLE_DELVE_GRID }, + action: { type: 'trigger-event', eventName: 'MAIN->OVERLAY::delve-grid' }, keepModKeys: true }) } @@ -149,7 +155,7 @@ function registerGlobal () { } else if (entry.action.type === 'paste-in-chat') { typeInChat(entry.action.text, entry.action.send) } else if (entry.action.type === 'trigger-event') { - overlayWindow!.webContents.send(entry.action.eventName) + overlaySendEvent({ name: entry.action.eventName, payload: undefined }) } else if (entry.action.type === 'copy-item') { const { action } = entry @@ -160,10 +166,13 @@ function registerGlobal () { pollClipboard() .then(clipboard => { - if (action.eventName.startsWith('price-check')) { + if (action.eventName === 'price-check-quick' || action.eventName === 'price-check-locked') { showPriceCheck({ clipboard, pressPosition, eventName: action.eventName }) } else { - overlayWindow!.webContents.send(action.eventName, { clipboard, position: pressPosition }) + overlaySendEvent({ + name: action.eventName, + payload: { clipboard, position: pressPosition } + }) if (action.focusOverlay) { assertOverlayActive() } @@ -233,7 +242,7 @@ export function setupShortcuts () { }) }) - ipcMain.on(ipc.STASH_SEARCH, (e, opts: ipc.IpcStashSearch) => { stashSearch(opts.text) }) + overlayOnEvent('OVERLAY->MAIN::stash-search', (_, { text }) => { stashSearch(text) }) uIOhook.on('keydown', (e) => { const pressed = eventToString(e) diff --git a/src/main/updates.ts b/src/main/updates.ts index 6c70fc21..7695c207 100644 --- a/src/main/updates.ts +++ b/src/main/updates.ts @@ -1,8 +1,7 @@ import { autoUpdater } from 'electron-updater' import { logger } from './logger' import { rebuildTrayMenu } from './tray' -import { UPDATE_AVAILABLE } from '@/ipc/ipc-event' -import { overlayWindow, overlayReady } from './overlay-window' +import { overlayReady, overlaySendEvent } from './overlay-window' import { config } from './config' export const UpdateState = { @@ -17,7 +16,10 @@ autoUpdater.on('update-available', async (info: { version: string }) => { } else { UpdateState.status = `Update v${info.version} available on GitHub` await overlayReady - overlayWindow!.webContents.send(UPDATE_AVAILABLE, { auto: false, version: info.version }) + overlaySendEvent({ + name: 'MAIN->OVERLAY::update-available', + payload: { auto: false, version: info.version } + }) } rebuildTrayMenu() }) @@ -39,7 +41,10 @@ autoUpdater.on('update-downloaded', async (info: { version: string }) => { UpdateState.status = `v${info.version} will be installed on exit` rebuildTrayMenu() await overlayReady - overlayWindow!.webContents.send(UPDATE_AVAILABLE, { auto: true, version: info.version }) + overlaySendEvent({ + name: 'MAIN->OVERLAY::update-available', + payload: { auto: true, version: info.version } + }) }) // on('download-progress') https://github.com/electron-userland/electron-builder/issues/2521 diff --git a/src/web/Config.ts b/src/web/Config.ts index a74dd770..80705100 100644 --- a/src/web/Config.ts +++ b/src/web/Config.ts @@ -1,5 +1,5 @@ import { reactive as deepReactive, shallowRef } from 'vue' -import { MainProcess } from '@/ipc/main-process-bindings' +import { MainProcess } from '@/web/background/IPC' import type { Config } from '@/ipc/types' import type { Widget } from './overlay/interfaces' diff --git a/src/web/background/AutoUpdates.ts b/src/web/background/AutoUpdates.ts index ac28f4dc..c9e9df91 100644 --- a/src/web/background/AutoUpdates.ts +++ b/src/web/background/AutoUpdates.ts @@ -1,9 +1,9 @@ -import { UPDATE_AVAILABLE, IpcUpdateInfo } from '@/ipc/ipc-event' -import { MainProcess } from '@/ipc/main-process-bindings' -import { ref } from 'vue' +import type { IpcUpdateInfo } from '@/ipc/ipc-event' +import { MainProcess } from '@/web/background/IPC' +import { shallowRef } from 'vue' -export const updateInfo = ref(null) +export const updateInfo = shallowRef(null) -MainProcess.addEventListener(UPDATE_AVAILABLE, (e) => { - updateInfo.value = (e as CustomEvent).detail +MainProcess.onEvent('MAIN->OVERLAY::update-available', (e) => { + updateInfo.value = e }) diff --git a/src/web/background/IPC.ts b/src/web/background/IPC.ts new file mode 100644 index 00000000..4ebb054a --- /dev/null +++ b/src/web/background/IPC.ts @@ -0,0 +1,85 @@ +import type { Renderer } from 'electron' +import type { IpcEvent, IpcEventPayload, IpcGetConfig, IpcImportFile } from '@/ipc/ipc-event' +import { Config, defaultConfig } from '@/ipc/types' + +let electron: typeof Renderer | undefined +try { + electron = require('electron') +} catch {} + +class MainProcessBinding { + private evBus = new EventTarget() + + constructor () { + if (electron) { + electron.ipcRenderer.on('named-event', (_e, data: IpcEvent) => { + this.selfDispatch(data) + }) + } + } + + selfDispatch (event: IpcEvent) { + this.evBus.dispatchEvent(new CustomEvent(event.name, { + detail: event.payload + })) + } + + sendEvent (event: IpcEvent) { + if (electron) { + electron.ipcRenderer.send(event.name, event.payload) + } + } + + onEvent ( + name: Name, + cb: (payload: IpcEventPayload) => void + ) { + this.evBus.addEventListener(name, (e) => { + cb((e as CustomEvent>).detail) + }) + } + + closeOverlay () { + this.sendEvent({ name: 'OVERLAY->MAIN::close-overlay', payload: undefined }) + } + + getConfig (): Config { + if (electron) { + const name: IpcGetConfig['name'] = 'OVERLAY->MAIN::get-config' + return electron.ipcRenderer.sendSync(name) as IpcGetConfig['payload'] + } else { + return defaultConfig() + } + } + + openSystemBrowser (url: string) { + if (electron) { + this.sendEvent({ name: 'OVERLAY->MAIN::system-browser', payload: url }) + } else { + window.open(url) + } + } + + saveConfig (config: Config) { + this.sendEvent({ name: 'OVERLAY->MAIN::save-config', payload: config }) + } + + importFile (filePath: string) { + if (electron) { + const name: IpcImportFile['name'] = 'OVERLAY->MAIN::import-file' + return electron.ipcRenderer.sendSync(name, filePath) as IpcImportFile['payload'] + } + } + + get CORS () { + return (!electron) + ? 'https://apt-cors.snos.workers.dev/?' + : '' + } + + get isElectron () { + return (electron != null) + } +} + +export const MainProcess = new MainProcessBinding() diff --git a/src/web/background/Prices.ts b/src/web/background/Prices.ts index bc65cb46..1b210ab7 100644 --- a/src/web/background/Prices.ts +++ b/src/web/background/Prices.ts @@ -1,5 +1,5 @@ import { ref, watch } from 'vue' -import { MainProcess } from '@/ipc/main-process-bindings' +import { MainProcess } from '@/web/background/IPC' import { selected as selectedLeague, isPublic as isPublicLeague } from './Leagues' interface NinjaCurrencyInfo { /* eslint-disable camelcase */ diff --git a/src/web/item-check/WidgetItemCheck.vue b/src/web/item-check/WidgetItemCheck.vue index ad3b0854..7a64e7d5 100644 --- a/src/web/item-check/WidgetItemCheck.vue +++ b/src/web/item-check/WidgetItemCheck.vue @@ -13,11 +13,9 @@ import { useI18n } from 'vue-i18n' import Widget from '../overlay/Widget.vue' import MapCheck from '../map-check/MapCheck.vue' import ItemInfo from './ItemInfo.vue' -import { MainProcess } from '@/ipc/main-process-bindings' -import { ITEM_CHECK } from '@/ipc/ipc-event' +import { MainProcess } from '@/web/background/IPC' import { ItemCategory, parseClipboard, ParsedItem } from '@/parser' import { ItemCheckWidget, WidgetManager } from '../overlay/interfaces' -import * as ipc from '@/ipc/ipc-event' export default defineComponent({ components: { @@ -38,13 +36,12 @@ export default defineComponent({ const checkPosition = ref({ x: 1, y: 1 }) const item = ref(null) - MainProcess.addEventListener(ITEM_CHECK, (e) => { - const _e = (e as CustomEvent).detail + MainProcess.onEvent('MAIN->OVERLAY::item-check', (e) => { checkPosition.value = { - x: _e.position.x - window.screenX, - y: _e.position.y - window.screenY + x: e.position.x - window.screenX, + y: e.position.y - window.screenY } - item.value = parseClipboard(_e.clipboard) + item.value = parseClipboard(e.clipboard) if (item.value) { wm.show(props.config.wmId) } diff --git a/src/web/other-services/craft-of-exile.ts b/src/web/other-services/craft-of-exile.ts index c3a7a8ff..3ecd9b3c 100644 --- a/src/web/other-services/craft-of-exile.ts +++ b/src/web/other-services/craft-of-exile.ts @@ -1,4 +1,4 @@ -import { MainProcess } from '@/ipc/main-process-bindings' +import { MainProcess } from '@/web/background/IPC' import { parseClipboard } from '@/parser' const COE_URL = 'https://craftofexile.com/' diff --git a/src/web/other-services/index.ts b/src/web/other-services/index.ts index 7a35d287..bd66a4b8 100644 --- a/src/web/other-services/index.ts +++ b/src/web/other-services/index.ts @@ -1,19 +1,20 @@ -import { MainProcess } from '@/ipc/main-process-bindings' -import * as ipc from '@/ipc/ipc-event' +import { MainProcess } from '@/web/background/IPC' import { openWiki } from './wiki' import { openCOE } from './craft-of-exile' import { handleLine } from '../client-log/client-log' export function registerOtherServices () { - MainProcess.addEventListener(ipc.OPEN_WIKI, (e) => { - openWiki((e as CustomEvent<{ clipboard: string }>).detail.clipboard) + MainProcess.onEvent('MAIN->OVERLAY::open-wiki', (e) => { + openWiki(e.clipboard) }) - MainProcess.addEventListener(ipc.CLIENT_LOG_UPDATE, (e) => { - handleLine((e as CustomEvent).detail) + MainProcess.onEvent('MAIN->OVERLAY::client-log', (e) => { + for (const line of e.lines) { + handleLine(line) + } }) - MainProcess.addEventListener(ipc.OPEN_COE, (e) => { - openCOE((e as CustomEvent<{ clipboard: string }>).detail.clipboard) + MainProcess.onEvent('MAIN->OVERLAY::open-craft-of-exile', (e) => { + openCOE(e.clipboard) }) } diff --git a/src/web/other-services/wiki.ts b/src/web/other-services/wiki.ts index 8fd3bed2..eda077b3 100644 --- a/src/web/other-services/wiki.ts +++ b/src/web/other-services/wiki.ts @@ -1,4 +1,4 @@ -import { MainProcess } from '@/ipc/main-process-bindings' +import { MainProcess } from '@/web/background/IPC' import { parseClipboard } from '@/parser' import { AppConfig } from '@/web/Config' diff --git a/src/web/overlay/OverlayWindow.vue b/src/web/overlay/OverlayWindow.vue index bcfda944..8951d0dc 100644 --- a/src/web/overlay/OverlayWindow.vue +++ b/src/web/overlay/OverlayWindow.vue @@ -20,7 +20,7 @@