refactor(stage-tamagotchi): better structure & lifecycle, set max listener for IpcRenderer
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import type { BrowserWindow } from 'electron'
|
||||
|
||||
import { env, platform } from 'node:process'
|
||||
import { platform } from 'node:process'
|
||||
|
||||
import { electronApp, optimizer } from '@electron-toolkit/utils'
|
||||
import { Format, LogLevel, setGlobalFormat, setGlobalLogLevel, useLogg } from '@guiiai/logg'
|
||||
@@ -15,6 +15,7 @@ import macOSTrayIcon from '../../resources/tray-icon-macos.png?asset'
|
||||
import { openDebugger, setupDebugger } from './app/debugger'
|
||||
import { emitAppBeforeQuit, emitAppReady, emitAppWindowAllClosed, onAppBeforeQuit } from './libs/bootkit/lifecycle'
|
||||
import { setElectronMainDirname } from './libs/electron/location'
|
||||
import { setupChannelServer } from './services/airi/channel-server'
|
||||
import { setupCaptionWindowManager } from './windows/caption'
|
||||
import { setupChatWindowReusableFunc } from './windows/chat'
|
||||
import { setupInlayWindow } from './windows/inlay'
|
||||
@@ -89,53 +90,12 @@ function setupTray(params: {
|
||||
})()
|
||||
}
|
||||
|
||||
async function setupProjectAIRIServerRuntime() {
|
||||
// Start the server-runtime server with WebSocket support
|
||||
try {
|
||||
// Dynamically import the server-runtime and listhen
|
||||
const serverRuntime = await import('@proj-airi/server-runtime')
|
||||
const { serve } = await import('h3')
|
||||
const { plugin: ws } = await import('crossws/server')
|
||||
|
||||
const serverInstance = serve(serverRuntime.app, {
|
||||
// TODO: fix types
|
||||
// @ts-expect-error - the .crossws property wasn't extended in types
|
||||
plugins: [ws({ resolve: async req => (await serverRuntime.app.fetch(req)).crossws })],
|
||||
port: env.PORT ? Number(env.PORT) : 6121,
|
||||
hostname: env.SERVER_RUNTIME_HOSTNAME || 'localhost',
|
||||
reusePort: true,
|
||||
})
|
||||
|
||||
log.log('@proj-airi/server-runtime started on ws://localhost:6121')
|
||||
|
||||
onAppBeforeQuit(async () => {
|
||||
if (serverInstance && typeof serverInstance.close === 'function') {
|
||||
try {
|
||||
await serverInstance.close()
|
||||
log.log('WebSocket server closed')
|
||||
}
|
||||
catch (error) {
|
||||
log.withError(error).error('Error closing WebSocket server')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
catch (error) {
|
||||
log.withError(error).error('failed to start WebSocket server')
|
||||
}
|
||||
}
|
||||
|
||||
app.whenReady().then(async () => {
|
||||
await setupProjectAIRIServerRuntime()
|
||||
|
||||
injecta.setLogger(createLoggLogger(useLogg('injecta').useGlobalConfig()))
|
||||
|
||||
const settingsWindow = injecta.provide('windows:settings', {
|
||||
build: () => setupSettingsWindowReusableFunc(),
|
||||
})
|
||||
const chatWindow = injecta.provide('windows:chat', {
|
||||
build: () => setupChatWindowReusableFunc(),
|
||||
})
|
||||
const channelServerModule = injecta.provide('modules:channel-server', async () => setupChannelServer())
|
||||
const settingsWindow = injecta.provide('windows:settings', () => setupSettingsWindowReusableFunc())
|
||||
const chatWindow = injecta.provide('windows:chat', { build: () => setupChatWindowReusableFunc() })
|
||||
const mainWindow = injecta.provide('windows:main', {
|
||||
dependsOn: { settingsWindow, chatWindow },
|
||||
build: async ({ dependsOn }) => setupMainWindow(dependsOn),
|
||||
@@ -149,7 +109,7 @@ app.whenReady().then(async () => {
|
||||
build: async ({ dependsOn }) => setupTray(dependsOn),
|
||||
})
|
||||
injecta.invoke({
|
||||
dependsOn: { mainWindow, tray },
|
||||
dependsOn: { mainWindow, tray, channelServerModule },
|
||||
callback: noop,
|
||||
})
|
||||
|
||||
@@ -183,4 +143,5 @@ app.on('window-all-closed', () => {
|
||||
// Clean up server and intervals when app quits
|
||||
app.on('before-quit', async () => {
|
||||
emitAppBeforeQuit()
|
||||
injecta.stop()
|
||||
})
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import { env } from 'node:process'
|
||||
|
||||
import { useLogg } from '@guiiai/logg'
|
||||
|
||||
import { onAppBeforeQuit } from '../../../libs/bootkit/lifecycle'
|
||||
|
||||
export async function setupChannelServer() {
|
||||
const log = useLogg('main/server-runtime').useGlobalConfig()
|
||||
|
||||
// Start the server-runtime server with WebSocket support
|
||||
try {
|
||||
// Dynamically import the server-runtime and listhen
|
||||
const serverRuntime = await import('@proj-airi/server-runtime')
|
||||
const { serve } = await import('h3')
|
||||
const { plugin: ws } = await import('crossws/server')
|
||||
|
||||
const serverInstance = serve(serverRuntime.app, {
|
||||
// TODO: fix types
|
||||
// @ts-expect-error - the .crossws property wasn't extended in types
|
||||
plugins: [ws({ resolve: async req => (await serverRuntime.app.fetch(req)).crossws })],
|
||||
port: env.PORT ? Number(env.PORT) : 6121,
|
||||
hostname: env.SERVER_RUNTIME_HOSTNAME || 'localhost',
|
||||
reusePort: true,
|
||||
gracefulShutdown: {
|
||||
forceTimeout: 500,
|
||||
gracefulTimeout: 500,
|
||||
},
|
||||
})
|
||||
|
||||
log.log('@proj-airi/server-runtime started on ws://localhost:6121')
|
||||
|
||||
onAppBeforeQuit(async () => {
|
||||
if (serverInstance && typeof serverInstance.close === 'function') {
|
||||
try {
|
||||
await serverInstance.close()
|
||||
log.log('WebSocket server closed')
|
||||
}
|
||||
catch (error) {
|
||||
log.withError(error).error('Error closing WebSocket server')
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
catch (error) {
|
||||
log.withError(error).error('failed to start WebSocket server')
|
||||
}
|
||||
}
|
||||
@@ -249,7 +249,7 @@ export function setupCaptionWindowManager(params: { mainWindow: BrowserWindow })
|
||||
// TODO: once we refactored eventa to support window-namespaced contexts,
|
||||
// we can remove the setMaxListeners call below since eventa will be able to dispatch and
|
||||
// manage events within eventa's context system.
|
||||
ipcMain.setMaxListeners(100)
|
||||
ipcMain.setMaxListeners(0)
|
||||
|
||||
const window = createCaptionWindow()
|
||||
const { context } = createContext(ipcMain, window)
|
||||
|
||||
@@ -143,6 +143,11 @@ export async function setupMainWindow(params: {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: once we refactored eventa to support window-namespaced contexts,
|
||||
// we can remove the setMaxListeners call below since eventa will be able to dispatch and
|
||||
// manage events within eventa's context system.
|
||||
ipcMain.setMaxListeners(0)
|
||||
|
||||
const { context } = createContext(ipcMain, window)
|
||||
const cleanUpWindowDraggingInvokeHandler = defineInvokeHandler(context, electronStartDraggingWindow, handleStartDraggingWindow)
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ export function setupMainWindowElectronInvokes(params: {
|
||||
// TODO: once we refactored eventa to support window-namespaced contexts,
|
||||
// we can remove the setMaxListeners call below since eventa will be able to dispatch and
|
||||
// manage events within eventa's context system.
|
||||
ipcMain.setMaxListeners(100)
|
||||
ipcMain.setMaxListeners(0)
|
||||
|
||||
const { context } = createContext(ipcMain, params.window)
|
||||
|
||||
|
||||
@@ -3,9 +3,14 @@ import type { ElectronWindow } from '@proj-airi/stage-shared'
|
||||
import { contextIsolated, platform } from 'node:process'
|
||||
|
||||
import { electronAPI } from '@electron-toolkit/preload'
|
||||
import { contextBridge } from 'electron'
|
||||
import { contextBridge, ipcRenderer } from 'electron'
|
||||
|
||||
export function expose<CustomApi = unknown>(customApi: CustomApi = undefined as CustomApi) {
|
||||
// TODO: once we refactored eventa to support window-namespaced contexts,
|
||||
// we can remove the setMaxListeners call below since eventa will be able to dispatch and
|
||||
// manage events within eventa's context system.
|
||||
ipcRenderer.setMaxListeners(0)
|
||||
|
||||
// Use `contextBridge` APIs to expose Electron APIs to
|
||||
// renderer only if context isolation is enabled, otherwise
|
||||
// just add to the DOM global.
|
||||
|
||||
@@ -3,13 +3,13 @@ import { useDisplayModelsStore } from '@proj-airi/stage-ui/stores/display-models
|
||||
import { useOnboardingStore } from '@proj-airi/stage-ui/stores/onboarding'
|
||||
import { useSettings } from '@proj-airi/stage-ui/stores/settings'
|
||||
import { defineInvoke, defineInvokeHandler } from '@unbird/eventa'
|
||||
import { createContext } from '@unbird/eventa/adapters/electron/renderer'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { onMounted, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { RouterView, useRouter } from 'vue-router'
|
||||
|
||||
import { electronOpenSettings, electronStartTrackMousePosition } from '../shared/eventa'
|
||||
import { useElectronEventaContext } from './composables/electron-vueuse'
|
||||
|
||||
const i18n = useI18n()
|
||||
const displayModelsStore = useDisplayModelsStore()
|
||||
@@ -29,14 +29,12 @@ onMounted(async () => {
|
||||
await displayModelsStore.loadDisplayModelsFromIndexedDB()
|
||||
await settingsStore.initializeStageModel()
|
||||
|
||||
const { context } = createContext(window.electron.ipcRenderer)
|
||||
const startTrackingCursorPoint = defineInvoke(context, electronStartTrackMousePosition)
|
||||
const context = useElectronEventaContext()
|
||||
const startTrackingCursorPoint = defineInvoke(context.value, electronStartTrackMousePosition)
|
||||
await startTrackingCursorPoint()
|
||||
|
||||
// Listen for open-settings IPC message from main process
|
||||
defineInvokeHandler(context, electronOpenSettings, () => {
|
||||
router.push('/settings')
|
||||
})
|
||||
defineInvokeHandler(context.value, electronOpenSettings, () => router.push('/settings'))
|
||||
})
|
||||
|
||||
watch(themeColorsHue, () => {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { defineInvoke } from '@unbird/eventa'
|
||||
import { createContext } from '@unbird/eventa/adapters/electron/renderer'
|
||||
import { useBroadcastChannel } from '@vueuse/core'
|
||||
import { onMounted, ref, watch } from 'vue'
|
||||
|
||||
import { captionGetIsFollowingWindow, captionIsFollowingWindowChanged } from '../../shared/eventa'
|
||||
import { useElectronEventaContext } from '../composables/electron-vueuse'
|
||||
|
||||
const attached = ref(true)
|
||||
const speakerText = ref('')
|
||||
@@ -16,8 +16,8 @@ type CaptionChannelEvent
|
||||
| { type: 'caption-assistant', text: string }
|
||||
const { data } = useBroadcastChannel<CaptionChannelEvent, CaptionChannelEvent>({ name: 'airi-caption-overlay' })
|
||||
|
||||
const { context } = createContext(window.electron.ipcRenderer)
|
||||
const getAttached = defineInvoke(context, captionGetIsFollowingWindow)
|
||||
const context = useElectronEventaContext()
|
||||
const getAttached = defineInvoke(context.value, captionGetIsFollowingWindow)
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
@@ -27,7 +27,7 @@ onMounted(async () => {
|
||||
catch {}
|
||||
|
||||
try {
|
||||
context.on(captionIsFollowingWindowChanged, (event) => {
|
||||
context.value.on(captionIsFollowingWindowChanged, (event) => {
|
||||
attached.value = Boolean(event?.body)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { ButtonBar, CheckBar, IconItem } from '@proj-airi/stage-ui/components'
|
||||
import { useSettings } from '@proj-airi/stage-ui/stores/settings'
|
||||
import { defineInvoke } from '@unbird/eventa'
|
||||
import { createContext } from '@unbird/eventa/adapters/electron/renderer'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { electronOpenMainDevtools } from '../../../../shared/eventa'
|
||||
import { useElectronEventaInvoke } from '../../../composables/electron-vueuse'
|
||||
|
||||
const { t } = useI18n()
|
||||
const settings = useSettings()
|
||||
@@ -44,8 +43,7 @@ const menu = computed(() => [
|
||||
},
|
||||
])
|
||||
|
||||
const { context } = createContext(window.electron.ipcRenderer)
|
||||
const openDevTools = defineInvoke(context, electronOpenMainDevtools)
|
||||
const openDevTools = useElectronEventaInvoke(electronOpenMainDevtools)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
Reference in New Issue
Block a user