UNPKG

@ably/cli

Version:

Ably CLI for Pub/Sub, Chat and Spaces

279 lines (278 loc) 12.8 kB
import { Flags } from "@oclif/core"; import * as Ably from "ably"; import chalk from "chalk"; import { AblyBaseCommand } from "../../base-command.js"; export default class ConnectionsTest extends AblyBaseCommand { static description = "Test connection to Ably"; static examples = [ "$ ably connections test", "$ ably connections test --transport ws", "$ ably connections test --transport xhr", ]; static flags = { ...AblyBaseCommand.globalFlags, transport: Flags.string({ default: "all", description: "Transport protocol to use (ws for WebSockets, xhr for HTTP)", options: ["ws", "xhr", "all"], }), }; wsClient = null; xhrClient = null; // Override finally to ensure resources are cleaned up async finally(err) { if (this.wsClient && this.wsClient.connection.state !== "closed" && this.wsClient.connection.state !== "failed") { this.wsClient.close(); } if (this.xhrClient && this.xhrClient.connection.state !== "closed" && this.xhrClient.connection.state !== "failed") { this.xhrClient.close(); } return super.finally(err); } async run() { const { flags } = await this.parse(ConnectionsTest); let wsSuccess = false; let xhrSuccess = false; let wsError = null; let xhrError = null; const baseOptions = this.getClientOptions(flags); try { // Run tests based on flags if (flags.transport === "all" || flags.transport === "ws") { const result = await this.testWebSocketConnection(baseOptions, flags); wsSuccess = result.success; wsError = result.error; } if (flags.transport === "all" || flags.transport === "xhr") { const result = await this.testXhrConnection(baseOptions, flags); xhrSuccess = result.success; xhrError = result.error; } this.outputSummary(flags, wsSuccess, xhrSuccess, wsError, xhrError); } catch (error) { const err = error; this.logCliEvent(flags || {}, "connectionTest", "fatalError", `Connection test failed: ${err.message}`, { error: err.message }); this.error(err.message); } finally { // Ensure clients are closed (handled by the finally override) } } // --- Refactored Test Methods --- outputSummary(flags, wsSuccess, xhrSuccess, wsError, xhrError) { const summary = { ws: { error: wsError?.message || null, success: wsSuccess }, xhr: { error: xhrError?.message || null, success: xhrSuccess }, }; this.logCliEvent(flags, "connectionTest", "summary", "Connection test summary", summary); if (this.shouldOutputJson(flags)) { // Output JSON summary let jsonOutput; switch (flags.transport) { case "all": { jsonOutput = { success: wsSuccess && xhrSuccess, transport: "all", ws: summary.ws, xhr: summary.xhr, }; break; } case "ws": { jsonOutput = { success: wsSuccess, transport: "ws", connectionId: wsSuccess ? this.wsClient?.connection.id : undefined, connectionKey: wsSuccess ? this.wsClient?.connection.key : undefined, error: wsError?.message || undefined, }; break; } case "xhr": { jsonOutput = { success: xhrSuccess, transport: "xhr", connectionId: xhrSuccess ? this.xhrClient?.connection.id : undefined, connectionKey: xhrSuccess ? this.xhrClient?.connection.key : undefined, error: xhrError?.message || undefined, }; break; } default: { jsonOutput = { success: false, error: "Unknown transport", }; } } this.log(this.formatJsonOutput(jsonOutput, flags)); } else { this.log(""); this.log("Connection Test Summary:"); switch (flags.transport) { case "all": { // If both were tested const allSuccess = wsSuccess && xhrSuccess; const partialSuccess = wsSuccess || xhrSuccess; if (allSuccess) { this.log(`${chalk.green("✓")} All connection tests passed successfully`); } else if (partialSuccess) { this.log(`${chalk.yellow("!")} Some connection tests succeeded, but others failed`); } else { this.log(`${chalk.red("✗")} All connection tests failed`); } break; } case "ws": { if (wsSuccess) { this.log(`${chalk.green("✓")} WebSocket connection test passed successfully`); } else { this.log(`${chalk.red("✗")} WebSocket connection test failed`); } break; } case "xhr": { if (xhrSuccess) { this.log(`${chalk.green("✓")} HTTP connection test passed successfully`); } else { this.log(`${chalk.red("✗")} HTTP connection test failed`); } break; } // No default } } } async testWebSocketConnection(baseOptions, flags) { let success = false; let errorResult = null; this.logCliEvent(flags, "connectionTest", "wsTestStarting", "Testing WebSocket connection..."); if (!this.shouldOutputJson(flags)) { this.log("Testing WebSocket connection to Ably..."); } try { const wsOptions = { ...baseOptions, transportParams: { disallowXHR: true, preferWebSockets: true, }, }; this.wsClient = new Ably.Realtime(wsOptions); const client = this.wsClient; client.connection.on((stateChange) => { this.logCliEvent(flags, "connectionTest", `wsStateChange-${stateChange.current}`, `WS connection state changed to ${stateChange.current}`, { reason: stateChange.reason }); }); await new Promise((resolve, reject) => { const connectionTimeout = setTimeout(() => { const timeoutError = new Error("Connection timeout after 10 seconds"); this.logCliEvent(flags, "connectionTest", "wsTimeout", timeoutError.message, { error: timeoutError.message }); reject(timeoutError); }, 10_000); client.connection.once("connected", () => { clearTimeout(connectionTimeout); success = true; this.logCliEvent(flags, "connectionTest", "wsSuccess", "WebSocket connection successful", { connectionId: client.connection.id }); if (!this.shouldOutputJson(flags)) { this.log(`${chalk.green("✓")} WebSocket connection successful`); this.log(` Connection ID: ${chalk.cyan(client.connection.id || "unknown")}`); } resolve(); }); client.connection.once("failed", (stateChange) => { clearTimeout(connectionTimeout); errorResult = stateChange.reason || new Error("Connection failed"); this.logCliEvent(flags, "connectionTest", "wsFailed", `WebSocket connection failed: ${errorResult.message}`, { error: errorResult.message, reason: stateChange.reason }); if (!this.shouldOutputJson(flags)) { this.log(`${chalk.red("✗")} WebSocket connection failed: ${errorResult.message}`); } resolve(); // Resolve even on failure to allow summary }); }); } catch (error) { errorResult = error; this.logCliEvent(flags, "connectionTest", "wsError", `WebSocket connection test caught error: ${errorResult.message}`, { error: errorResult.message }); if (!this.shouldOutputJson(flags)) { this.log(`${chalk.red("✗")} WebSocket connection failed: ${errorResult.message}`); } } finally { // Close client if it exists and isn't already closed if (this.wsClient && this.wsClient.connection.state !== "closed") { this.wsClient.close(); } } return { error: errorResult, success }; } async testXhrConnection(baseOptions, flags) { let success = false; let errorResult = null; this.logCliEvent(flags, "connectionTest", "xhrTestStarting", "Testing HTTP connection..."); if (!this.shouldOutputJson(flags)) { this.log("Testing HTTP connection to Ably..."); } try { const xhrOptions = { ...baseOptions, transportParams: { disallowWebSockets: true, }, }; this.xhrClient = new Ably.Realtime(xhrOptions); const client = this.xhrClient; client.connection.on((stateChange) => { this.logCliEvent(flags, "connectionTest", `xhrStateChange-${stateChange.current}`, `HTTP connection state changed to ${stateChange.current}`, { reason: stateChange.reason }); }); await new Promise((resolve, reject) => { const connectionTimeout = setTimeout(() => { const timeoutError = new Error("Connection timeout after 10 seconds"); this.logCliEvent(flags, "connectionTest", "xhrTimeout", timeoutError.message, { error: timeoutError.message }); reject(timeoutError); }, 10_000); client.connection.once("connected", () => { clearTimeout(connectionTimeout); success = true; this.logCliEvent(flags, "connectionTest", "xhrSuccess", "HTTP connection successful", { connectionId: client.connection.id }); if (!this.shouldOutputJson(flags)) { this.log(`${chalk.green("✓")} HTTP connection successful`); this.log(` Connection ID: ${chalk.cyan(client.connection.id || "unknown")}`); } resolve(); }); client.connection.once("failed", (stateChange) => { clearTimeout(connectionTimeout); errorResult = stateChange.reason || new Error("Connection failed"); this.logCliEvent(flags, "connectionTest", "xhrFailed", `HTTP connection failed: ${errorResult.message}`, { error: errorResult.message, reason: stateChange.reason }); if (!this.shouldOutputJson(flags)) { this.log(`${chalk.red("✗")} HTTP connection failed: ${errorResult.message}`); } resolve(); // Resolve even on failure }); }); } catch (error) { errorResult = error; this.logCliEvent(flags, "connectionTest", "xhrError", `HTTP connection test caught error: ${errorResult.message}`, { error: errorResult.message }); if (!this.shouldOutputJson(flags)) { this.log(`${chalk.red("✗")} HTTP connection failed: ${errorResult.message}`); } } finally { if (this.xhrClient && this.xhrClient.connection.state !== "closed") { this.xhrClient.close(); } } return { error: errorResult, success }; } }