diff --git a/plugin/core/runtime.ts b/plugin/core/runtime.ts index a3db311..5e7edbc 100644 --- a/plugin/core/runtime.ts +++ b/plugin/core/runtime.ts @@ -1,6 +1,7 @@ import { YONEXUS_PROTOCOL_VERSION, buildAuthRequest, + buildHeartbeat, buildHello, buildPairConfirm, createAuthRequestSigningInput, @@ -109,6 +110,7 @@ export class YonexusClientRuntime { async handleMessage(raw: string): Promise { if (raw === "heartbeat_tick") { + await this.handleHeartbeatTick(); return; } @@ -150,14 +152,12 @@ export class YonexusClientRuntime { } if (envelope.type === "auth_failed") { - this.handleAuthFailed(envelope as TypedBuiltinEnvelope<"auth_failed">); + await this.handleAuthFailed(envelope as TypedBuiltinEnvelope<"auth_failed">); return; } if (envelope.type === "re_pair_required") { - this.pendingPairing = undefined; - this.lastPairingFailure = "re_pair_required"; - this.phase = "pair_required"; + await this.handleRePairRequired(); return; } } @@ -283,14 +283,22 @@ export class YonexusClientRuntime { this.phase = "waiting_pair_confirm"; } - private handleAuthFailed(envelope: TypedBuiltinEnvelope<"auth_failed">): void { + private async handleAuthFailed( + envelope: TypedBuiltinEnvelope<"auth_failed"> + ): Promise { const payload = envelope.payload as AuthFailedPayload | undefined; if (!payload) { return; } + if (payload.reason === "re_pair_required") { + this.lastPairingFailure = payload.reason; + await this.resetTrustState(); + return; + } + this.lastPairingFailure = payload.reason; - this.phase = payload.reason === "re_pair_required" ? "pair_required" : "auth_required"; + this.phase = "auth_required"; } private async sendAuthRequest(): Promise { @@ -330,6 +338,43 @@ export class YonexusClientRuntime { ) ); } + + private async handleHeartbeatTick(): Promise { + if (this.phase !== "authenticated" || !this.options.transport.isConnected) { + return; + } + + this.options.transport.send( + encodeBuiltin( + buildHeartbeat( + { + identifier: this.options.config.identifier, + status: "alive" + }, + { timestamp: this.now() } + ) + ) + ); + } + + private async handleRePairRequired(): Promise { + this.pendingPairing = undefined; + this.lastPairingFailure = "re_pair_required"; + await this.resetTrustState(); + } + + private async resetTrustState(): Promise { + this.clientState = { + ...this.clientState, + secret: undefined, + pairedAt: undefined, + authenticatedAt: undefined, + updatedAt: this.now() + }; + + await this.options.stateStore.save(this.clientState); + this.phase = "pair_required"; + } } export function createYonexusClientRuntime(