Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,9 @@ $ ably-interactive
* [`ably rooms occupancy subscribe ROOM`](#ably-rooms-occupancy-subscribe-room)
* [`ably rooms presence`](#ably-rooms-presence)
* [`ably rooms presence enter ROOM`](#ably-rooms-presence-enter-room)
* [`ably rooms presence get-all ROOM`](#ably-rooms-presence-get-all-room)
* [`ably rooms presence subscribe ROOM`](#ably-rooms-presence-subscribe-room)
* [`ably rooms presence update ROOM`](#ably-rooms-presence-update-room)
* [`ably rooms reactions`](#ably-rooms-reactions)
* [`ably rooms reactions send ROOM EMOJI`](#ably-rooms-reactions-send-room-emoji)
* [`ably rooms reactions subscribe ROOM`](#ably-rooms-reactions-subscribe-room)
Expand Down Expand Up @@ -2037,6 +2039,8 @@ EXAMPLES

$ ably channels presence update my-channel --data '{"status":"busy"}' --json

$ ably channels presence update my-channel --data '{"status":"busy"}' --pretty-json

$ ably channels presence update my-channel --data '{"status":"online"}' --duration 60
```

Expand Down Expand Up @@ -4241,10 +4245,44 @@ EXAMPLES
$ ably rooms presence enter my-room --duration 30

$ ably rooms presence enter my-room --json

$ ably rooms presence enter my-room --pretty-json
```

_See code: [src/commands/rooms/presence/enter.ts](https://github.com/ably/ably-cli/blob/v0.17.0/src/commands/rooms/presence/enter.ts)_

## `ably rooms presence get-all ROOM`

Get all current presence members in a chat room

```
USAGE
$ ably rooms presence get-all ROOM [-v] [--json | --pretty-json] [--limit <value>]

ARGUMENTS
ROOM Room to get presence members for

FLAGS
-v, --verbose Output verbose logs
--json Output in JSON format
--limit=<value> [default: 100] Maximum number of results to return
--pretty-json Output in colorized JSON format

DESCRIPTION
Get all current presence members in a chat room

EXAMPLES
$ ably rooms presence get-all my-room

$ ably rooms presence get-all my-room --limit 50

$ ably rooms presence get-all my-room --json

$ ably rooms presence get-all my-room --pretty-json
```

_See code: [src/commands/rooms/presence/get-all.ts](https://github.com/ably/ably-cli/blob/v0.17.0/src/commands/rooms/presence/get-all.ts)_

## `ably rooms presence subscribe ROOM`

Subscribe to presence events in a chat room
Expand Down Expand Up @@ -4277,6 +4315,41 @@ EXAMPLES

_See code: [src/commands/rooms/presence/subscribe.ts](https://github.com/ably/ably-cli/blob/v0.17.0/src/commands/rooms/presence/subscribe.ts)_

## `ably rooms presence update ROOM`

Update presence data in a chat room

```
USAGE
$ ably rooms presence update ROOM --data <value> [-v] [--json | --pretty-json] [--client-id <value>] [-D <value>]

ARGUMENTS
ROOM Room to update presence in

FLAGS
-D, --duration=<value> Automatically exit after N seconds
-v, --verbose Output verbose logs
--client-id=<value> Overrides any default client ID when using API authentication. Use "none" to explicitly set
no client ID. Not applicable when using token authentication.
--data=<value> (required) JSON data to associate with the presence update
--json Output in JSON format
--pretty-json Output in colorized JSON format

DESCRIPTION
Update presence data in a chat room

EXAMPLES
$ ably rooms presence update my-room --data '{"status":"away"}'

$ ably rooms presence update my-room --data '{"status":"busy"}' --json

$ ably rooms presence update my-room --data '{"status":"busy"}' --pretty-json

$ ably rooms presence update my-room --data '{"status":"online"}' --duration 60
```

_See code: [src/commands/rooms/presence/update.ts](https://github.com/ably/ably-cli/blob/v0.17.0/src/commands/rooms/presence/update.ts)_

## `ably rooms reactions`

Manage reactions in Ably chat rooms
Expand Down
159 changes: 82 additions & 77 deletions src/commands/rooms/presence/enter.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import { ChatClient, Room, PresenceEvent, PresenceData } from "@ably/chat";
import { Args, Flags, Interfaces } from "@oclif/core";
import { Args, Flags } from "@oclif/core";
import { productApiFlags, clientIdFlag, durationFlag } from "../../../flags.js";
import { ChatBaseCommand } from "../../../chat-base-command.js";
import { isJsonData } from "../../../utils/json-formatter.js";
import {
formatSuccess,
formatListening,
formatProgress,
formatResource,
formatTimestamp,
formatPresenceAction,
formatEventType,
formatIndex,
formatClientId,
formatLabel,
formatMessageTimestamp,
} from "../../../utils/output.js";

export default class RoomsPresenceEnter extends ChatBaseCommand {
Expand All @@ -29,6 +32,7 @@ export default class RoomsPresenceEnter extends ChatBaseCommand {
"$ ably rooms presence enter my-room --show-others",
"$ ably rooms presence enter my-room --duration 30",
"$ ably rooms presence enter my-room --json",
"$ ably rooms presence enter my-room --pretty-json",
];
static override flags = {
...productApiFlags,
Expand All @@ -54,19 +58,14 @@ export default class RoomsPresenceEnter extends ChatBaseCommand {
private roomName: string | null = null;
private data: PresenceData | null = null;

private commandFlags: Interfaces.InferredFlags<
typeof RoomsPresenceEnter.flags
> | null = null;
private sequenceCounter = 0;

async run(): Promise<void> {
const { args, flags } = await this.parse(RoomsPresenceEnter);
this.commandFlags = flags;
this.roomName = args.room;

const rawData = flags.data;
if (rawData && rawData !== "{}") {
const parsed = this.parseJsonFlag(rawData, "data", flags);
if (flags.data) {
const parsed = this.parseJsonFlag(flags.data, "data", flags);
this.data = parsed as PresenceData;
}

Expand Down Expand Up @@ -101,11 +100,15 @@ export default class RoomsPresenceEnter extends ChatBaseCommand {
const member = event.member;
if (member.clientId !== this.chatClient?.clientId) {
this.sequenceCounter++;
const timestamp = new Date().toISOString();
const eventData = {
eventType: event.type,
member: { clientId: member.clientId, data: member.data },
const timestamp = formatMessageTimestamp(
member.updatedAt?.getTime(),
);
const presenceEvent = {
action: event.type,
room: this.roomName,
clientId: member.clientId,
connectionId: member.connectionId,
data: member.data ?? null,
timestamp,
...(flags["sequence-numbers"]
? { sequence: this.sequenceCounter }
Expand All @@ -115,103 +118,105 @@ export default class RoomsPresenceEnter extends ChatBaseCommand {
flags,
"presence",
event.type,
`Presence event '${event.type}' received`,
eventData,
`Presence event: ${event.type} by ${member.clientId}`,
presenceEvent,
);
if (this.shouldOutputJson(flags)) {
this.logJsonEvent(eventData, flags);
this.logJsonEvent({ presenceMessage: presenceEvent }, flags);
} else {
const { symbol: actionSymbol, color: actionColor } =
formatPresenceAction(event.type);
const sequencePrefix = flags["sequence-numbers"]
? `${formatIndex(this.sequenceCounter)}`
: "";
this.log(
`${formatTimestamp(timestamp)}${sequencePrefix} ${actionColor(actionSymbol)} ${formatClientId(member.clientId || "Unknown")} ${actionColor(event.type)}`,
`${formatTimestamp(timestamp)}${sequencePrefix} ${formatResource(`Room: ${this.roomName!}`)} | Action: ${formatEventType(event.type)} | Client: ${formatClientId(member.clientId || "N/A")}`,
);
if (
member.data &&
typeof member.data === "object" &&
Object.keys(member.data).length > 0
) {
const profile = member.data as { name?: string };
if (profile.name) {
this.log(` ${formatLabel("Name")} ${profile.name}`);

if (member.data !== null && member.data !== undefined) {
if (isJsonData(member.data)) {
this.log(formatLabel("Data"));
this.log(JSON.stringify(member.data, null, 2));
} else {
this.log(`${formatLabel("Data")} ${member.data}`);
}
this.log(
` ${formatLabel("Full Data")} ${this.formatJsonOutput({ data: member.data }, flags)}`,
);
}

this.log(""); // Empty line for better readability
}
}
});
}

await currentRoom.attach();

if (!this.shouldOutputJson(flags)) {
this.log(
formatProgress(
`Entering presence in room: ${formatResource(this.roomName)}`,
),
);
}

this.logCliEvent(flags, "presence", "entering", "Entering presence", {
room: this.roomName,
clientId: this.chatClient!.clientId,
data: this.data,
});
await currentRoom.presence.enter(this.data || {});
this.logCliEvent(flags, "presence", "entered", "Entered presence");
await currentRoom.presence.enter(this.data ?? undefined);
this.logCliEvent(flags, "presence", "entered", "Entered presence", {
room: this.roomName,
clientId: this.chatClient!.clientId,
});

if (!this.shouldOutputJson(flags) && this.roomName) {
if (this.shouldOutputJson(flags)) {
this.logJsonResult(
{
presenceMessage: {
action: "enter",
room: this.roomName,
clientId: this.chatClient!.clientId,
connectionId: this.chatClient!.realtime.connection.id,
data: this.data ?? null,
timestamp: new Date().toISOString(),
},
},
flags,
);
} else {
this.log(
formatSuccess(
`Entered presence in room: ${formatResource(this.roomName)}.`,
`Entered presence in room: ${formatResource(this.roomName!)}.`,
),
);
if (flags["show-others"]) {
this.log(`\n${formatListening("Listening for presence events.")}`);
} else {
this.log(`\n${formatListening("Staying present.")}`);
this.log(
`${formatLabel("Client ID")} ${formatClientId(this.chatClient!.clientId ?? "unknown")}`,
);
this.log(
`${formatLabel("Connection ID")} ${this.chatClient!.realtime.connection.id}`,
);
if (this.data !== undefined && this.data !== null) {
this.log(`${formatLabel("Data")} ${JSON.stringify(this.data)}`);
}
this.log(
formatListening(
flags["show-others"]
? "Listening for presence events."
: "Holding presence.",
),
);
}

this.logJsonStatus(
"holding",
"Holding presence. Press Ctrl+C to exit.",
flags,
);

// Wait until the user interrupts or the optional duration elapses
await this.waitAndTrackCleanup(flags, "presence", flags.duration);
} catch (error) {
this.fail(error, flags, "roomPresenceEnter", {
room: this.roomName,
});
} finally {
const currentFlags = this.commandFlags || flags || {};
this.logCliEvent(
currentFlags,
"presence",
"finallyBlockReached",
"Reached finally block for cleanup.",
);

if (!this.cleanupInProgress && !this.shouldOutputJson(currentFlags)) {
this.logCliEvent(
currentFlags,
"presence",
"implicitCleanupInFinally",
"Performing cleanup in finally (no prior signal or natural end).",
);
} else {
// Either cleanup is in progress or we're in JSON mode
this.logCliEvent(
currentFlags,
"presence",
"explicitCleanupOrJsonMode",
"Cleanup already in progress or JSON output mode",
);
}

if (!this.shouldOutputJson(currentFlags)) {
if (this.cleanupInProgress) {
this.log(formatSuccess("Graceful shutdown complete."));
} else {
// Normal completion without user interrupt
this.logCliEvent(
currentFlags,
"presence",
"completedNormally",
"Command completed normally",
);
}
}
}
}
}
Loading
Loading