@ably/cli
Version:
Ably CLI for Pub/Sub, Chat and Spaces
279 lines (278 loc) • 12.8 kB
JavaScript
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 };
}
}