feat(sdk): add auto-verify handshake after gateway registration

Embed transparent verification logic in GatewayClient that automatically
sends an RPC "verify" request to the Hub after REGISTERED event. Adds
hubId, token, and verifyTimeout options. Upper-layer callers see no change
— "registered" state means both gateway registration and Hub verification
are complete. Failures surface via onError callback.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
yushen 2026-02-04 13:27:23 +08:00
parent e9ed53b615
commit 0eac2b2a23
4 changed files with 60 additions and 3 deletions

View file

@ -25,6 +25,8 @@ export {
type DeleteAgentResult,
type UpdateGatewayParams,
type UpdateGatewayResult,
type VerifyParams,
type VerifyResult,
} from "./rpc";
export {

View file

@ -145,3 +145,14 @@ export interface UpdateGatewayResult {
url: string;
connectionState: string;
}
/** verify - request params */
export interface VerifyParams {
token?: string;
}
/** verify - response payload */
export interface VerifyResult {
hubId: string;
agentId: string;
}

View file

@ -34,6 +34,9 @@ interface ResolvedOptions {
deviceType: DeviceType;
autoReconnect: boolean;
reconnectDelay: number;
hubId: string | undefined;
token: string | undefined;
verifyTimeout: number;
}
export class GatewayClient {
@ -55,6 +58,9 @@ export class GatewayClient {
deviceType: options.deviceType,
autoReconnect: options.autoReconnect ?? true,
reconnectDelay: options.reconnectDelay ?? 1000,
hubId: options.hubId,
token: options.token,
verifyTimeout: options.verifyTimeout ?? 30_000,
};
}
@ -227,6 +233,12 @@ export class GatewayClient {
return this;
}
/** Hub 验证成功回调 */
onVerified(callback: (result: { hubId: string; agentId: string }) => void): this {
this.callbacks.onVerified = callback;
return this;
}
/** 注册消息回调 */
onMessage(callback: (message: RoutedMessage) => void): this {
this.callbacks.onMessage = callback;
@ -291,11 +303,36 @@ export class GatewayClient {
this.socket.on(
GatewayEvents.REGISTERED,
(response: RegisteredResponse) => {
if (response.success) {
if (!response.success) {
this.callbacks.onError?.(new Error(response.error ?? "Registration failed"));
return;
}
// If hubId is configured, auto-verify before exposing "registered" to upper layer
if (this.options.hubId) {
// Set internal state to allow send/request during verify
this._state = "registered";
this.request<{ hubId: string; agentId: string }>(
this.options.hubId,
"verify",
{ token: this.options.token },
this.options.verifyTimeout,
)
.then((result) => {
// Verify succeeded — now expose "registered" to upper layer
this.callbacks.onVerified?.(result);
this.callbacks.onRegistered?.(response.deviceId);
this.callbacks.onStateChange?.("registered");
})
.catch((err) => {
// Verify failed (UNAUTHORIZED, REJECTED, or timeout)
this.callbacks.onError?.(err instanceof Error ? err : new Error(String(err)));
this.disconnect();
});
} else {
// No hubId — original behavior
this.setState("registered");
this.callbacks.onRegistered?.(response.deviceId);
} else {
this.callbacks.onError?.(new Error(response.error ?? "Registration failed"));
}
}
);

View file

@ -89,6 +89,12 @@ export interface GatewayClientOptions {
autoReconnect?: boolean | undefined;
/** Reconnect delay (milliseconds), defaults to 1000 */
reconnectDelay?: number | undefined;
/** Hub device ID for verification (optional, enables auto-verify after gateway registration) */
hubId?: string | undefined;
/** Token for first-time verification (optional, omit for reconnection via device whitelist) */
token?: string | undefined;
/** Verify timeout in ms (default: 30_000, longer because user confirmation may be needed) */
verifyTimeout?: number | undefined;
}
/** Connection state */
@ -103,6 +109,7 @@ export interface GatewayClientCallbacks {
onConnect?: (socketId: string) => void;
onDisconnect?: (reason: string) => void;
onRegistered?: (deviceId: string) => void;
onVerified?: (result: { hubId: string; agentId: string }) => void;
onMessage?: (message: RoutedMessage) => void;
onSendError?: (error: SendErrorResponse) => void;
onPong?: (data: string) => void;