UNPKG

@ably/cli

Version:

Ably CLI for Pub/Sub, Chat and Spaces

145 lines (144 loc) 7.9 kB
import { Args, Flags } from "@oclif/core"; import chalk from "chalk"; import { ChatBaseCommand } from "../../../chat-base-command.js"; import { waitUntilInterruptedOrTimeout } from "../../../utils/long-running.js"; export default class MessagesSubscribe extends ChatBaseCommand { static args = { room: Args.string({ description: "The room to subscribe to messages from", required: true, }), }; static description = "Subscribe to messages in an Ably Chat room"; static examples = [ "$ ably rooms messages subscribe my-room", '$ ably rooms messages subscribe --api-key "YOUR_API_KEY" my-room', "$ ably rooms messages subscribe --show-metadata my-room", "$ ably rooms messages subscribe my-room --duration 30", "$ ably rooms messages subscribe my-room --json", "$ ably rooms messages subscribe my-room --pretty-json", ]; static flags = { ...ChatBaseCommand.globalFlags, "show-metadata": Flags.boolean({ default: false, description: "Display message metadata if available", }), duration: Flags.integer({ description: "Automatically exit after the given number of seconds (0 = run indefinitely)", char: "D", required: false, }), }; chatClient = null; roomName = null; cleanupInProgress = false; async run() { const { args, flags } = await this.parse(MessagesSubscribe); this.roomName = args.room; // Store for cleanup this.logCliEvent(flags, "subscribe.run", "start", `Starting rooms messages subscribe for room: ${this.roomName}`); try { // Create clients this.logCliEvent(flags, "subscribe.auth", "attemptingClientCreation", "Attempting to create Chat and Ably clients."); // Create Chat client (which also creates the Ably client internally) this.chatClient = await this.createChatClient(flags); this.logCliEvent(flags, "subscribe.auth", "clientCreationSuccess", "Chat and Ably clients created."); if (!this.shouldOutputJson(flags)) { this.log(`Attaching to room: ${chalk.cyan(this.roomName)}...`); } if (!this.chatClient) { throw new Error("Failed to create Chat or Ably client"); } // Set up connection state logging this.setupConnectionStateLogging(this.chatClient.realtime, flags, { includeUserFriendlyMessages: true, }); // Get the room this.logCliEvent(flags, "room", "gettingRoom", `Getting room handle for ${this.roomName}`); const room = await this.chatClient.rooms.get(this.roomName); this.logCliEvent(flags, "room", "gotRoom", `Got room handle for ${this.roomName}`); // Setup message handler this.logCliEvent(flags, "room", "subscribingToMessages", `Subscribing to messages in room ${this.roomName}`); room.messages.subscribe((messageEvent) => { const { message } = messageEvent; const messageLog = { clientId: message.clientId, text: message.text, timestamp: message.timestamp, ...(message.metadata ? { metadata: message.metadata } : {}), }; this.logCliEvent(flags, "message", "received", "Message received", { message: messageLog, room: this.roomName, }); if (this.shouldOutputJson(flags)) { this.log(this.formatJsonOutput({ message: messageLog, room: this.roomName, success: true, }, flags)); } else { // Format message with timestamp, author and content const timestamp = new Date(message.timestamp).toLocaleTimeString(); const author = message.clientId || "Unknown"; // Message content with consistent formatting this.log(`${chalk.gray(`[${timestamp}]`)} ${chalk.cyan(`${author}:`)} ${message.text}`); // Show metadata if enabled and available if (flags["show-metadata"] && message.metadata) { this.log(`${chalk.blue(" Metadata:")} ${chalk.yellow(this.formatJsonOutput(message.metadata, flags))}`); } this.log(""); // Empty line for better readability } }); this.logCliEvent(flags, "room", "subscribedToMessages", `Successfully subscribed to messages in room ${this.roomName}`); // Subscribe to room status changes this.logCliEvent(flags, "room", "subscribingToStatus", `Subscribing to status changes for room ${this.roomName}`); room.onStatusChange((statusChange) => { const change = statusChange; this.logCliEvent(flags, "room", `status-${change.current}`, `Room status changed to ${change.current}`, { reason: change.reason, room: this.roomName }); if (change.current === "attached") { this.logCliEvent(flags, "room", "statusAttached", "Room status is ATTACHED."); // Log the ready signal for E2E tests this.log(`Connected to room: ${this.roomName}`); if (!this.shouldOutputJson(flags)) { this.log(chalk.green(`✓ Subscribed to room: ${chalk.cyan(this.roomName)}. Listening for messages...`)); } // If we want to suppress output, we just don't log anything } else if (change.current === "failed") { const errorMsg = room.error?.message || "Unknown error"; if (this.shouldOutputJson(flags)) { // Logged via logCliEvent } else { this.error(`Failed to attach to room: ${errorMsg}`); } } }); this.logCliEvent(flags, "room", "subscribedToStatus", `Successfully subscribed to status changes for room ${this.roomName}`); // Attach to the room this.logCliEvent(flags, "room", "attaching", `Attaching to room ${this.roomName}`); await room.attach(); this.logCliEvent(flags, "room", "attachCallComplete", `room.attach() call complete for ${this.roomName}. Waiting for status change to 'attached'.`); // Note: successful attach logged by onStatusChange handler this.logCliEvent(flags, "subscribe", "listening", "Now listening for messages and status changes"); // Wait until the user interrupts or the optional duration elapses const exitReason = await waitUntilInterruptedOrTimeout(flags.duration); this.logCliEvent(flags, "subscribe", "runComplete", "Exiting wait loop", { exitReason, }); this.cleanupInProgress = exitReason === "signal"; // mark if signal so finally knows } catch (error) { const errorMsg = error instanceof Error ? error.message : String(error); this.logCliEvent(flags, "subscribe", "fatalError", `Failed to subscribe to messages: ${errorMsg}`, { error: errorMsg, room: this.roomName }); if (this.shouldOutputJson(flags)) { this.log(this.formatJsonOutput({ error: errorMsg, room: this.roomName, success: false }, flags)); } else { this.error(`Failed to subscribe to messages: ${errorMsg}`); } } } }