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([ 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 enabled: false
}, },
{ type: 'separator' }, { type: 'separator' },
{ {
label: status.running ? 'Stop Relay' : 'Start Relay', label: status.running || status.waiting ? 'Stop Relay' : 'Start Relay',
click: async () => { click: async () => {
try { try {
if (status.running) { if (status.running || status.waiting) {
await relay.stop() await relay.stop()
} else { } else {
await relay.start() await relay.start()
+39 -4
View File
@@ -7,6 +7,7 @@ import { platform, type ClaimedSocket, type ProcessInfo } from './platform'
const MAX_SOCKETS = 10 const MAX_SOCKETS = 10
const DISCOVERY_INTERVAL_MS = 3000 const DISCOVERY_INTERVAL_MS = 3000
const WAITING_RETRY_INTERVAL_MS = 3000
interface AppAsset { interface AppAsset {
id: string id: string
@@ -101,6 +102,7 @@ export interface LastActivity {
export interface RelayStatus { export interface RelayStatus {
running: boolean running: boolean
waiting: boolean
unsupported: boolean unsupported: boolean
instances: RelayInstance[] instances: RelayInstance[]
connectedClients: ConnectedClient[] connectedClients: ConnectedClient[]
@@ -120,8 +122,10 @@ export class RpcRelay extends EventEmitter {
private connectedClients = new Map<number, ConnectedClient>() private connectedClients = new Map<number, ConnectedClient>()
private nextClientId = 1 private nextClientId = 1
private discoveryTimer: NodeJS.Timeout | null = null private discoveryTimer: NodeJS.Timeout | null = null
private waitingTimer: NodeJS.Timeout | null = null
private lastActivity: LastActivity | null = null private lastActivity: LastActivity | null = null
private running = false private running = false
private waiting = false
private lastError: string | null = null private lastError: string | null = null
private restarting = false private restarting = false
private mirrors = new Map<number, MirrorConnection>() private mirrors = new Map<number, MirrorConnection>()
@@ -138,6 +142,7 @@ export class RpcRelay extends EventEmitter {
return { return {
running: this.running, running: this.running,
waiting: this.waiting,
unsupported: !platform.isSupported, unsupported: !platform.isSupported,
instances, instances,
connectedClients: [...this.connectedClients.values()], connectedClients: [...this.connectedClients.values()],
@@ -181,11 +186,13 @@ export class RpcRelay extends EventEmitter {
this.claimed = platform.discoverAndClaim() this.claimed = platform.discoverAndClaim()
if (this.claimed.length === 0) { if (this.claimed.length === 0) {
this.lastError = 'No running Discord clients found (no discord-ipc-N sockets)' this.waitForDiscord()
this.emitStatus() return
throw new Error(this.lastError)
} }
this.waiting = false
this.stopWaitingTimer()
const fake = platform.fakeSocketPath() const fake = platform.fakeSocketPath()
platform.removeFakeSocket(fake) platform.removeFakeSocket(fake)
@@ -211,7 +218,13 @@ export class RpcRelay extends EventEmitter {
} }
async stop(): Promise<void> { async stop(): Promise<void> {
if (!this.running) return this.stopWaitingTimer()
this.waiting = false
if (!this.running) {
this.emitStatus()
return
}
this.running = false this.running = false
this.stopDiscoveryTimer() this.stopDiscoveryTimer()
@@ -236,6 +249,28 @@ export class RpcRelay extends EventEmitter {
return this.claimed[0]?.index 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 { private startDiscoveryTimer(): void {
this.stopDiscoveryTimer() this.stopDiscoveryTimer()
this.discoveryTimer = setInterval(() => this.discoverNewInstances(), DISCOVERY_INTERVAL_MS) 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 = { const EMPTY_STATUS: RelayStatus = {
running: false, running: false,
waiting: false,
unsupported: false, unsupported: false,
instances: [], instances: [],
connectedClients: [], connectedClients: [],
@@ -120,7 +121,7 @@ export function App(): React.JSX.Element {
const toggleRelay = async (): Promise<void> => { const toggleRelay = async (): Promise<void> => {
setLoading(true) setLoading(true)
try { try {
if (status.running) { if (status.running || status.waiting) {
await window.api.stop() await window.api.stop()
} else { } else {
await window.api.start() 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"> <section className="rounded-xl bg-zinc-800/60 border border-zinc-700 p-4 flex items-center justify-between">
<div className="flex flex-col"> <div className="flex flex-col">
<span className="text-sm">Relay</span> <span className="text-sm">Relay</span>
<span className={`text-xs ${status.running ? 'text-emerald-400' : 'text-zinc-500'}`}> <span
{status.running ? 'Running' : 'Stopped'} 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> </span>
</div> </div>
<Toggle checked={status.running} onChange={() => toggleRelay()} disabled={loading} /> <Toggle
checked={status.running || status.waiting}
onChange={() => toggleRelay()}
disabled={loading}
/>
</section> </section>
<section className="rounded-xl bg-zinc-800/60 border border-zinc-700 p-4 flex flex-col gap-2"> <section className="rounded-xl bg-zinc-800/60 border border-zinc-700 p-4 flex flex-col gap-2">