Skip to content

Commit 4afc78c

Browse files
committed
Implemented rooms presence get-all, rooms presence update commands
1 parent bc7a212 commit 4afc78c

7 files changed

Lines changed: 962 additions & 162 deletions

File tree

README.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,9 @@ $ ably-interactive
193193
* [`ably rooms occupancy subscribe ROOM`](#ably-rooms-occupancy-subscribe-room)
194194
* [`ably rooms presence`](#ably-rooms-presence)
195195
* [`ably rooms presence enter ROOM`](#ably-rooms-presence-enter-room)
196+
* [`ably rooms presence get-all ROOM`](#ably-rooms-presence-get-all-room)
196197
* [`ably rooms presence subscribe ROOM`](#ably-rooms-presence-subscribe-room)
198+
* [`ably rooms presence update ROOM`](#ably-rooms-presence-update-room)
197199
* [`ably rooms reactions`](#ably-rooms-reactions)
198200
* [`ably rooms reactions send ROOM EMOJI`](#ably-rooms-reactions-send-room-emoji)
199201
* [`ably rooms reactions subscribe ROOM`](#ably-rooms-reactions-subscribe-room)
@@ -4241,6 +4243,38 @@ EXAMPLES
42414243

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

4246+
## `ably rooms presence get-all ROOM`
4247+
4248+
Get all current presence members in a chat room
4249+
4250+
```
4251+
USAGE
4252+
$ ably rooms presence get-all ROOM [-v] [--json | --pretty-json] [--limit <value>]
4253+
4254+
ARGUMENTS
4255+
ROOM Room to get presence members for
4256+
4257+
FLAGS
4258+
-v, --verbose Output verbose logs
4259+
--json Output in JSON format
4260+
--limit=<value> [default: 100] Maximum number of results to return
4261+
--pretty-json Output in colorized JSON format
4262+
4263+
DESCRIPTION
4264+
Get all current presence members in a chat room
4265+
4266+
EXAMPLES
4267+
$ ably rooms presence get-all my-room
4268+
4269+
$ ably rooms presence get-all my-room --limit 50
4270+
4271+
$ ably rooms presence get-all my-room --json
4272+
4273+
$ ably rooms presence get-all my-room --pretty-json
4274+
```
4275+
4276+
_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)_
4277+
42444278
## `ably rooms presence subscribe ROOM`
42454279

42464280
Subscribe to presence events in a chat room
@@ -4273,6 +4307,39 @@ EXAMPLES
42734307

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

4310+
## `ably rooms presence update ROOM`
4311+
4312+
Update presence data in a chat room
4313+
4314+
```
4315+
USAGE
4316+
$ ably rooms presence update ROOM --data <value> [-v] [--json | --pretty-json] [--client-id <value>] [-D <value>]
4317+
4318+
ARGUMENTS
4319+
ROOM Room to update presence in
4320+
4321+
FLAGS
4322+
-D, --duration=<value> Automatically exit after N seconds
4323+
-v, --verbose Output verbose logs
4324+
--client-id=<value> Overrides any default client ID when using API authentication. Use "none" to explicitly set
4325+
no client ID. Not applicable when using token authentication.
4326+
--data=<value> (required) JSON data to associate with the presence update
4327+
--json Output in JSON format
4328+
--pretty-json Output in colorized JSON format
4329+
4330+
DESCRIPTION
4331+
Update presence data in a chat room
4332+
4333+
EXAMPLES
4334+
$ ably rooms presence update my-room --data '{"status":"away"}'
4335+
4336+
$ ably rooms presence update my-room --data '{"status":"busy"}' --json
4337+
4338+
$ ably rooms presence update my-room --data '{"status":"online"}' --duration 60
4339+
```
4340+
4341+
_See code: [src/commands/rooms/presence/update.ts](https://github.com/ably/ably-cli/blob/v0.17.0/src/commands/rooms/presence/update.ts)_
4342+
42764343
## `ably rooms reactions`
42774344

42784345
Manage reactions in Ably chat rooms

src/commands/rooms/presence/enter.ts

Lines changed: 33 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { ChatBaseCommand } from "../../../chat-base-command.js";
55
import {
66
formatSuccess,
77
formatListening,
8+
formatProgress,
89
formatResource,
910
formatTimestamp,
1011
formatPresenceAction,
@@ -148,70 +149,59 @@ export default class RoomsPresenceEnter extends ChatBaseCommand {
148149
}
149150

150151
await currentRoom.attach();
152+
153+
if (!this.shouldOutputJson(flags)) {
154+
this.log(
155+
formatProgress(
156+
`Entering presence in room: ${formatResource(this.roomName)}`,
157+
),
158+
);
159+
}
160+
151161
this.logCliEvent(flags, "presence", "entering", "Entering presence", {
152162
data: this.data,
153163
});
154164
await currentRoom.presence.enter(this.data || {});
155165
this.logCliEvent(flags, "presence", "entered", "Entered presence");
156166

157-
if (!this.shouldOutputJson(flags) && this.roomName) {
167+
if (this.shouldOutputJson(flags)) {
168+
this.logJsonResult(
169+
{
170+
presenceMessage: {
171+
action: "enter",
172+
room: this.roomName,
173+
clientId: this.chatClient!.clientId,
174+
data: this.data ?? null,
175+
timestamp: new Date().toISOString(),
176+
},
177+
},
178+
flags,
179+
);
180+
} else {
158181
this.log(
159182
formatSuccess(
160-
`Entered presence in room: ${formatResource(this.roomName)}.`,
183+
`Entered presence in room: ${formatResource(this.roomName!)}.`,
161184
),
162185
);
163186
if (flags["show-others"]) {
164-
this.log(`\n${formatListening("Listening for presence events.")}`);
187+
this.log(formatListening("Listening for presence events."));
165188
} else {
166-
this.log(`\n${formatListening("Staying present.")}`);
189+
this.log(formatListening("Staying present."));
167190
}
168191
}
169192

193+
this.logJsonStatus(
194+
"holding",
195+
"Holding presence. Press Ctrl+C to exit.",
196+
flags,
197+
);
198+
170199
// Wait until the user interrupts or the optional duration elapses
171200
await this.waitAndTrackCleanup(flags, "presence", flags.duration);
172201
} catch (error) {
173202
this.fail(error, flags, "roomPresenceEnter", {
174203
room: this.roomName,
175204
});
176-
} finally {
177-
const currentFlags = this.commandFlags || flags || {};
178-
this.logCliEvent(
179-
currentFlags,
180-
"presence",
181-
"finallyBlockReached",
182-
"Reached finally block for cleanup.",
183-
);
184-
185-
if (!this.cleanupInProgress && !this.shouldOutputJson(currentFlags)) {
186-
this.logCliEvent(
187-
currentFlags,
188-
"presence",
189-
"implicitCleanupInFinally",
190-
"Performing cleanup in finally (no prior signal or natural end).",
191-
);
192-
} else {
193-
// Either cleanup is in progress or we're in JSON mode
194-
this.logCliEvent(
195-
currentFlags,
196-
"presence",
197-
"explicitCleanupOrJsonMode",
198-
"Cleanup already in progress or JSON output mode",
199-
);
200-
}
201-
202-
if (!this.shouldOutputJson(currentFlags)) {
203-
if (this.cleanupInProgress) {
204-
this.log(formatSuccess("Graceful shutdown complete."));
205-
} else {
206-
// Normal completion without user interrupt
207-
this.logCliEvent(
208-
currentFlags,
209-
"presence",
210-
"completedNormally",
211-
"Command completed normally",
212-
);
213-
}
214-
}
215205
}
216206
}
217207
}
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
import { Args, Flags } from "@oclif/core";
2+
3+
import { AblyBaseCommand } from "../../../base-command.js";
4+
import { productApiFlags } from "../../../flags.js";
5+
import {
6+
formatClientId,
7+
formatCountLabel,
8+
formatHeading,
9+
formatIndex,
10+
formatLabel,
11+
formatLimitWarning,
12+
formatMessageTimestamp,
13+
formatProgress,
14+
formatResource,
15+
formatWarning,
16+
} from "../../../utils/output.js";
17+
import {
18+
buildPaginationNext,
19+
collectPaginatedResults,
20+
formatPaginationWarning,
21+
} from "../../../utils/pagination.js";
22+
23+
// Chat SDK maps room presence to the underlying channel: roomName::$chat
24+
const chatChannelName = (roomName: string) => `${roomName}::$chat`;
25+
26+
export default class RoomsPresenceGetAll extends AblyBaseCommand {
27+
static override args = {
28+
room: Args.string({
29+
description: "Room to get presence members for",
30+
required: true,
31+
}),
32+
};
33+
34+
static override description =
35+
"Get all current presence members in a chat room";
36+
37+
static override examples = [
38+
"$ ably rooms presence get-all my-room",
39+
"$ ably rooms presence get-all my-room --limit 50",
40+
"$ ably rooms presence get-all my-room --json",
41+
"$ ably rooms presence get-all my-room --pretty-json",
42+
];
43+
44+
static override flags = {
45+
...productApiFlags,
46+
limit: Flags.integer({
47+
default: 100,
48+
description: "Maximum number of results to return",
49+
min: 1,
50+
}),
51+
};
52+
53+
async run(): Promise<void> {
54+
const { args, flags } = await this.parse(RoomsPresenceGetAll);
55+
56+
try {
57+
const client = await this.createAblyRestClient(flags);
58+
if (!client) return;
59+
60+
const { room: roomName } = args;
61+
const channelName = chatChannelName(roomName);
62+
63+
if (!this.shouldOutputJson(flags)) {
64+
this.log(
65+
formatProgress(
66+
`Fetching presence members for room: ${formatResource(roomName)}`,
67+
),
68+
);
69+
}
70+
71+
this.logCliEvent(
72+
flags,
73+
"presence",
74+
"fetching",
75+
`Fetching presence members for room ${roomName}`,
76+
{ room: roomName },
77+
);
78+
79+
const firstPage = await client.channels
80+
.get(channelName)
81+
.presence.get({ limit: flags.limit });
82+
83+
const { items, hasMore, pagesConsumed } = await collectPaginatedResults(
84+
firstPage,
85+
flags.limit,
86+
);
87+
88+
this.logCliEvent(
89+
flags,
90+
"presence",
91+
"fetched",
92+
`Fetched ${items.length} presence members`,
93+
{ room: roomName, count: items.length },
94+
);
95+
96+
// Show pagination warning early (before main output)
97+
const paginationWarning = formatPaginationWarning(
98+
pagesConsumed,
99+
items.length,
100+
);
101+
if (paginationWarning && !this.shouldOutputJson(flags)) {
102+
this.log(paginationWarning);
103+
}
104+
105+
if (this.shouldOutputJson(flags)) {
106+
const presenceMembers = items.map((member) => ({
107+
clientId: member.clientId,
108+
connectionId: member.connectionId,
109+
data: member.data ?? null,
110+
extras: member.extras ?? null,
111+
updatedAt: formatMessageTimestamp(member.timestamp),
112+
}));
113+
const next = buildPaginationNext(hasMore);
114+
this.logJsonResult(
115+
{
116+
presenceMembers,
117+
hasMore,
118+
...(next && { next }),
119+
total: items.length,
120+
},
121+
flags,
122+
);
123+
} else if (items.length === 0) {
124+
this.logToStderr(
125+
formatWarning("No members currently present in this room."),
126+
);
127+
} else {
128+
this.log(
129+
`\n${formatHeading(`Presence members in room: ${roomName}`)} (${formatCountLabel(items.length, "member")}):\n`,
130+
);
131+
132+
for (let i = 0; i < items.length; i++) {
133+
const member = items[i];
134+
this.log(`${formatIndex(i + 1)}`);
135+
this.log(
136+
` ${formatLabel("Client ID")} ${formatClientId(member.clientId)}`,
137+
);
138+
this.log(` ${formatLabel("Connection ID")} ${member.connectionId}`);
139+
if (member.data !== null && member.data !== undefined) {
140+
this.log(` ${formatLabel("Data")} ${JSON.stringify(member.data)}`);
141+
}
142+
if (
143+
member.extras !== null &&
144+
member.extras !== undefined &&
145+
typeof member.extras === "object" &&
146+
Object.keys(member.extras).length > 0
147+
) {
148+
this.log(
149+
` ${formatLabel("Extras")} ${JSON.stringify(member.extras)}`,
150+
);
151+
}
152+
this.log(
153+
` ${formatLabel("Updated At")} ${formatMessageTimestamp(member.timestamp)}`,
154+
);
155+
this.log("");
156+
}
157+
158+
if (hasMore) {
159+
const warning = formatLimitWarning(
160+
items.length,
161+
flags.limit,
162+
"members",
163+
);
164+
if (warning) this.log(warning);
165+
}
166+
}
167+
} catch (error) {
168+
this.fail(error, flags, "roomPresenceGetAll", {
169+
room: args.room,
170+
});
171+
}
172+
}
173+
}

0 commit comments

Comments
 (0)