dev/2026-04-08 #1

Merged
hzhang merged 24 commits from dev/2026-04-08 into main 2026-04-13 09:34:01 +00:00
2 changed files with 76 additions and 1 deletions
Showing only changes of commit 5ca6ec0952 - Show all commits

View File

@@ -1,10 +1,13 @@
import { import {
YONEXUS_PROTOCOL_VERSION, YONEXUS_PROTOCOL_VERSION,
buildAuthRequest,
buildHello, buildHello,
buildPairConfirm, buildPairConfirm,
createAuthRequestSigningInput,
decodeBuiltin, decodeBuiltin,
encodeBuiltin, encodeBuiltin,
isBuiltinMessage, isBuiltinMessage,
type AuthFailedPayload,
type HelloAckPayload, type HelloAckPayload,
type PairFailedPayload, type PairFailedPayload,
type PairRequestPayload, type PairRequestPayload,
@@ -20,6 +23,7 @@ import {
YonexusClientState, YonexusClientState,
YonexusClientStateStore YonexusClientStateStore
} from "./state.js"; } from "./state.js";
import { generateNonce, signMessage } from "../crypto/keypair.js";
import type { ClientConnectionState, ClientTransport } from "./transport.js"; import type { ClientConnectionState, ClientTransport } from "./transport.js";
export type YonexusClientPhase = export type YonexusClientPhase =
@@ -134,9 +138,28 @@ export class YonexusClientRuntime {
} }
if (envelope.type === "auth_success") { if (envelope.type === "auth_success") {
this.options.transport.markAuthenticated();
this.clientState = {
...this.clientState,
authenticatedAt: this.now(),
updatedAt: this.now()
};
await this.options.stateStore.save(this.clientState);
this.phase = "authenticated"; this.phase = "authenticated";
return; return;
} }
if (envelope.type === "auth_failed") {
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";
return;
}
} }
handleTransportStateChange(state: ClientConnectionState): void { handleTransportStateChange(state: ClientConnectionState): void {
@@ -145,7 +168,7 @@ export class YonexusClientRuntime {
} }
if (state === "disconnected") { if (state === "disconnected") {
this.phase = "idle"; this.phase = hasClientSecret(this.clientState) ? "auth_required" : "idle";
} }
} }
@@ -202,6 +225,7 @@ export class YonexusClientRuntime {
break; break;
case "auth_required": case "auth_required":
this.phase = "auth_required"; this.phase = "auth_required";
void this.sendAuthRequest();
break; break;
default: default:
this.phase = "idle"; this.phase = "idle";
@@ -240,6 +264,7 @@ export class YonexusClientRuntime {
this.pendingPairing = undefined; this.pendingPairing = undefined;
this.lastPairingFailure = undefined; this.lastPairingFailure = undefined;
this.phase = "auth_required"; this.phase = "auth_required";
await this.sendAuthRequest();
} }
private handlePairFailed(envelope: TypedBuiltinEnvelope<"pair_failed">): void { private handlePairFailed(envelope: TypedBuiltinEnvelope<"pair_failed">): void {
@@ -257,6 +282,54 @@ export class YonexusClientRuntime {
this.phase = "waiting_pair_confirm"; this.phase = "waiting_pair_confirm";
} }
private handleAuthFailed(envelope: TypedBuiltinEnvelope<"auth_failed">): void {
const payload = envelope.payload as AuthFailedPayload | undefined;
if (!payload) {
return;
}
this.lastPairingFailure = payload.reason;
this.phase = payload.reason === "re_pair_required" ? "pair_required" : "auth_required";
}
private async sendAuthRequest(): Promise<void> {
if (!this.options.transport.isConnected) {
return;
}
if (!this.clientState.secret || !this.clientState.privateKey) {
this.phase = "pair_required";
return;
}
const proofTimestamp = this.now();
const nonce = generateNonce();
const signature = await signMessage(
this.clientState.privateKey,
createAuthRequestSigningInput({
secret: this.clientState.secret,
nonce,
proofTimestamp
})
);
this.options.transport.markAuthenticating();
this.options.transport.send(
encodeBuiltin(
buildAuthRequest(
{
identifier: this.options.config.identifier,
nonce,
proofTimestamp,
signature,
publicKey: this.clientState.publicKey
},
{ requestId: `auth_${proofTimestamp}_${nonce}`, timestamp: proofTimestamp }
)
)
);
}
} }
export function createYonexusClientRuntime( export function createYonexusClientRuntime(

View File

@@ -18,6 +18,8 @@ export interface ClientTransport {
connect(): Promise<void>; connect(): Promise<void>;
disconnect(): void; disconnect(): void;
send(message: string): boolean; send(message: string): boolean;
markAuthenticated(): void;
markAuthenticating(): void;
} }
export type ClientMessageHandler = (message: string) => void; export type ClientMessageHandler = (message: string) => void;