feat: Implement waiting state

This commit is contained in:
2026-06-16 03:26:06 +07:00
parent ece8225ea2
commit 97ada7e635
3 changed files with 63 additions and 11 deletions
+7 -3
View File
@@ -150,15 +150,19 @@ function updateTrayMenu(status: RelayStatus): void {
const menu = Menu.buildFromTemplate([
{
label: status.running ? 'Relay: Running' : 'Relay: Stopped',
label: status.running
? 'Relay: Running'
: status.waiting
? 'Relay: Waiting for Discord…'
: 'Relay: Stopped',
enabled: false
},
{ type: 'separator' },
{
label: status.running ? 'Stop Relay' : 'Start Relay',
label: status.running || status.waiting ? 'Stop Relay' : 'Start Relay',
click: async () => {
try {
if (status.running) {
if (status.running || status.waiting) {
await relay.stop()
} else {
await relay.start()
+39 -4
View File
@@ -7,6 +7,7 @@ import { platform, type ClaimedSocket, type ProcessInfo } from './platform'
const MAX_SOCKETS = 10
const DISCOVERY_INTERVAL_MS = 3000
const WAITING_RETRY_INTERVAL_MS = 3000
interface AppAsset {
id: string
@@ -101,6 +102,7 @@ export interface LastActivity {
export interface RelayStatus {
running: boolean
waiting: boolean
unsupported: boolean
instances: RelayInstance[]
connectedClients: ConnectedClient[]
@@ -120,8 +122,10 @@ export class RpcRelay extends EventEmitter {
private connectedClients = new Map<number, ConnectedClient>()
private nextClientId = 1
private discoveryTimer: NodeJS.Timeout | null = null
private waitingTimer: NodeJS.Timeout | null = null
private lastActivity: LastActivity | null = null
private running = false
private waiting = false
private lastError: string | null = null
private restarting = false
private mirrors = new Map<number, MirrorConnection>()
@@ -138,6 +142,7 @@ export class RpcRelay extends EventEmitter {
return {
running: this.running,
waiting: this.waiting,
unsupported: !platform.isSupported,
instances,
connectedClients: [...this.connectedClients.values()],
@@ -181,11 +186,13 @@ export class RpcRelay extends EventEmitter {
this.claimed = platform.discoverAndClaim()
if (this.claimed.length === 0) {
this.lastError = 'No running Discord clients found (no discord-ipc-N sockets)'
this.emitStatus()
throw new Error(this.lastError)
this.waitForDiscord()
return
}
this.waiting = false
this.stopWaitingTimer()
const fake = platform.fakeSocketPath()
platform.removeFakeSocket(fake)
@@ -211,7 +218,13 @@ export class RpcRelay extends EventEmitter {
}
async stop(): Promise<void> {
if (!this.running) return
this.stopWaitingTimer()
this.waiting = false
if (!this.running) {
this.emitStatus()
return
}
this.running = false
this.stopDiscoveryTimer()
@@ -236,6 +249,28 @@ export class RpcRelay extends EventEmitter {
return this.claimed[0]?.index
}
private waitForDiscord(): void {
this.waiting = true
this.lastError = null
this.emitStatus()
if (this.waitingTimer) return
this.waitingTimer = setInterval(() => {
if (!this.waiting) return
void this.start().catch((err) => {
this.lastError = err instanceof Error ? err.message : String(err)
this.emitStatus()
})
}, WAITING_RETRY_INTERVAL_MS)
}
private stopWaitingTimer(): void {
if (this.waitingTimer) {
clearInterval(this.waitingTimer)
this.waitingTimer = null
}
}
private startDiscoveryTimer(): void {
this.stopDiscoveryTimer()
this.discoveryTimer = setInterval(() => this.discoverNewInstances(), DISCOVERY_INTERVAL_MS)
+17 -4
View File
@@ -7,6 +7,7 @@ import { Button, LinkButton } from './components/Button'
const EMPTY_STATUS: RelayStatus = {
running: false,
waiting: false,
unsupported: false,
instances: [],
connectedClients: [],
@@ -120,7 +121,7 @@ export function App(): React.JSX.Element {
const toggleRelay = async (): Promise<void> => {
setLoading(true)
try {
if (status.running) {
if (status.running || status.waiting) {
await window.api.stop()
} else {
await window.api.start()
@@ -227,11 +228,23 @@ export function App(): React.JSX.Element {
<section className="rounded-xl bg-zinc-800/60 border border-zinc-700 p-4 flex items-center justify-between">
<div className="flex flex-col">
<span className="text-sm">Relay</span>
<span className={`text-xs ${status.running ? 'text-emerald-400' : 'text-zinc-500'}`}>
{status.running ? 'Running' : 'Stopped'}
<span
className={`text-xs ${
status.running
? 'text-emerald-400'
: status.waiting
? 'text-amber-400'
: 'text-zinc-500'
}`}
>
{status.running ? 'Running' : status.waiting ? 'Waiting for Discord…' : 'Stopped'}
</span>
</div>
<Toggle checked={status.running} onChange={() => toggleRelay()} disabled={loading} />
<Toggle
checked={status.running || status.waiting}
onChange={() => toggleRelay()}
disabled={loading}
/>
</section>
<section className="rounded-xl bg-zinc-800/60 border border-zinc-700 p-4 flex flex-col gap-2">