feat(cli+broker): kick, ban, unban, bans commands
Broker WS handlers: - kick: disconnect peer(s) by name, --stale duration, or --all. Authz: owner or admin only. Closes WS + marks presence disconnected. - ban: kick + set revokedAt on mesh.member. Hello already rejects revoked members, so ban is instant and permanent until unban. - unban: clear revokedAt. Peer can rejoin with their existing keypair. - list_bans: return all revoked members for a mesh. Session-id dedup (previous commit): handleHello disconnects ghost presences with matching (meshId, sessionId) before inserting the new one. Eliminates duplicate entries after broker restarts. CLI (alpha.37): - claudemesh kick <peer|--stale 30m|--all> - claudemesh ban/unban <peer> - claudemesh bans [--json] - Uses new sendAndWait() on ws-client for request-response pattern over WS (generic _reqId resolver). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1408,6 +1408,28 @@ export class BrokerClient {
|
||||
this.ws.send(JSON.stringify(payload));
|
||||
}
|
||||
|
||||
/**
|
||||
* Public request-response: sends a raw message with a generated _reqId
|
||||
* and resolves when the broker responds with any message containing the
|
||||
* same _reqId. Used by kick/ban/unban/bans CLI commands.
|
||||
*/
|
||||
async sendAndWait<T = Record<string, unknown>>(payload: Record<string, unknown>, timeoutMs = 10_000): Promise<T> {
|
||||
const reqId = `rw-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
const timer = setTimeout(() => {
|
||||
this.genericResolvers.delete(reqId);
|
||||
reject(new Error("sendAndWait timeout"));
|
||||
}, timeoutMs);
|
||||
this.genericResolvers.set(reqId, (msg) => {
|
||||
clearTimeout(timer);
|
||||
this.genericResolvers.delete(reqId);
|
||||
resolve(msg as T);
|
||||
});
|
||||
this.sendRaw({ ...payload, _reqId: reqId });
|
||||
});
|
||||
}
|
||||
private genericResolvers = new Map<string, (msg: Record<string, unknown>) => void>();
|
||||
|
||||
close(): void {
|
||||
this.closed = true;
|
||||
this.stopStatsReporting();
|
||||
@@ -1627,6 +1649,13 @@ export class BrokerClient {
|
||||
private handleServerMessage(msg: Record<string, unknown>): void {
|
||||
const msgReqId = msg._reqId as string | undefined;
|
||||
|
||||
// Generic request-response resolver (kick_ack, ban_ack, unban_ack, etc.)
|
||||
if (msgReqId && this.genericResolvers.has(msgReqId)) {
|
||||
const resolve = this.genericResolvers.get(msgReqId)!;
|
||||
resolve(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg.type === "ack") {
|
||||
const pending = this.pendingSends.get(String(msg.id ?? ""));
|
||||
if (pending) {
|
||||
|
||||
Reference in New Issue
Block a user