Compare commits
1 Commits
cec59784de
...
5ca6ec0952
| Author | SHA1 | Date | |
|---|---|---|---|
| 5ca6ec0952 |
@@ -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(
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user