diff --git a/README.md b/README.md index 86c7cd0d..f909eb25 100644 --- a/README.md +++ b/README.md @@ -121,7 +121,7 @@ $ ably-interactive * [`ably channels occupancy subscribe CHANNEL`](#ably-channels-occupancy-subscribe-channel) * [`ably channels presence`](#ably-channels-presence) * [`ably channels presence enter CHANNEL`](#ably-channels-presence-enter-channel) -* [`ably channels presence get-all CHANNEL`](#ably-channels-presence-get-all-channel) +* [`ably channels presence get CHANNEL`](#ably-channels-presence-get-channel) * [`ably channels presence subscribe CHANNEL`](#ably-channels-presence-subscribe-channel) * [`ably channels presence update CHANNEL`](#ably-channels-presence-update-channel) * [`ably channels publish CHANNEL MESSAGE`](#ably-channels-publish-channel-message) @@ -193,7 +193,7 @@ $ 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 get ROOM`](#ably-rooms-presence-get-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) @@ -1940,13 +1940,13 @@ EXAMPLES _See code: [src/commands/channels/presence/enter.ts](https://github.com/ably/ably-cli/blob/v0.17.0/src/commands/channels/presence/enter.ts)_ -## `ably channels presence get-all CHANNEL` +## `ably channels presence get CHANNEL` Get all current presence members on a channel ``` USAGE - $ ably channels presence get-all CHANNEL [-v] [--json | --pretty-json] [--limit ] + $ ably channels presence get CHANNEL [-v] [--json | --pretty-json] [--limit ] ARGUMENTS CHANNEL Channel name to get presence members for @@ -1961,16 +1961,16 @@ DESCRIPTION Get all current presence members on a channel EXAMPLES - $ ably channels presence get-all my-channel + $ ably channels presence get my-channel - $ ably channels presence get-all my-channel --limit 50 + $ ably channels presence get my-channel --limit 50 - $ ably channels presence get-all my-channel --json + $ ably channels presence get my-channel --json - $ ably channels presence get-all my-channel --pretty-json + $ ably channels presence get my-channel --pretty-json ``` -_See code: [src/commands/channels/presence/get-all.ts](https://github.com/ably/ably-cli/blob/v0.17.0/src/commands/channels/presence/get-all.ts)_ +_See code: [src/commands/channels/presence/get.ts](https://github.com/ably/ably-cli/blob/v0.17.0/src/commands/channels/presence/get.ts)_ ## `ably channels presence subscribe CHANNEL` @@ -4251,13 +4251,13 @@ EXAMPLES _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` +## `ably rooms presence get ROOM` Get all current presence members in a chat room ``` USAGE - $ ably rooms presence get-all ROOM [-v] [--json | --pretty-json] [--limit ] + $ ably rooms presence get ROOM [-v] [--json | --pretty-json] [--limit ] ARGUMENTS ROOM Room to get presence members for @@ -4272,16 +4272,16 @@ DESCRIPTION Get all current presence members in a chat room EXAMPLES - $ ably rooms presence get-all my-room + $ ably rooms presence get my-room - $ ably rooms presence get-all my-room --limit 50 + $ ably rooms presence get my-room --limit 50 - $ ably rooms presence get-all my-room --json + $ ably rooms presence get my-room --json - $ ably rooms presence get-all my-room --pretty-json + $ ably rooms presence get 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)_ +_See code: [src/commands/rooms/presence/get.ts](https://github.com/ably/ably-cli/blob/v0.17.0/src/commands/rooms/presence/get.ts)_ ## `ably rooms presence subscribe ROOM` @@ -4321,7 +4321,7 @@ Update presence data in a chat room ``` USAGE - $ ably rooms presence update ROOM --data [-v] [--json | --pretty-json] [--client-id ] [-D ] + $ ably rooms presence update ROOM --client-id [-v] [--json | --pretty-json] [--data ] [-D ] ARGUMENTS ROOM Room to update presence in @@ -4329,9 +4329,9 @@ ARGUMENTS FLAGS -D, --duration= Automatically exit after N seconds -v, --verbose Output verbose logs - --client-id= Overrides any default client ID when using API authentication. Use "none" to explicitly set - no client ID. Not applicable when using token authentication. - --data= (required) JSON data to associate with the presence update + --client-id= (required) Overrides any default client ID when using API authentication. Use "none" to + explicitly set no client ID. Not applicable when using token authentication. + --data= JSON data to associate with the presence update --json Output in JSON format --pretty-json Output in colorized JSON format diff --git a/src/commands/channels/presence/get-all.ts b/src/commands/channels/presence/get.ts similarity index 92% rename from src/commands/channels/presence/get-all.ts rename to src/commands/channels/presence/get.ts index d234e2d3..df3c61d4 100644 --- a/src/commands/channels/presence/get-all.ts +++ b/src/commands/channels/presence/get.ts @@ -20,7 +20,7 @@ import { formatPaginationLog, } from "../../../utils/pagination.js"; -export default class ChannelsPresenceGetAll extends AblyBaseCommand { +export default class ChannelsPresenceGet extends AblyBaseCommand { static override args = { channel: Args.string({ description: "Channel name to get presence members for", @@ -31,10 +31,10 @@ export default class ChannelsPresenceGetAll extends AblyBaseCommand { static override description = "Get all current presence members on a channel"; static override examples = [ - "$ ably channels presence get-all my-channel", - "$ ably channels presence get-all my-channel --limit 50", - "$ ably channels presence get-all my-channel --json", - "$ ably channels presence get-all my-channel --pretty-json", + "$ ably channels presence get my-channel", + "$ ably channels presence get my-channel --limit 50", + "$ ably channels presence get my-channel --json", + "$ ably channels presence get my-channel --pretty-json", ]; static override flags = { @@ -47,7 +47,7 @@ export default class ChannelsPresenceGetAll extends AblyBaseCommand { }; async run(): Promise { - const { args, flags } = await this.parse(ChannelsPresenceGetAll); + const { args, flags } = await this.parse(ChannelsPresenceGet); try { const client = await this.createAblyRestClient(flags); @@ -154,7 +154,7 @@ export default class ChannelsPresenceGetAll extends AblyBaseCommand { } } } catch (error) { - this.fail(error, flags, "presenceGetAll", { + this.fail(error, flags, "presenceGet", { channel: args.channel, }); } diff --git a/src/commands/channels/presence/update.ts b/src/commands/channels/presence/update.ts index fe188067..499d6a9f 100644 --- a/src/commands/channels/presence/update.ts +++ b/src/commands/channels/presence/update.ts @@ -30,10 +30,12 @@ export default class ChannelsPresenceUpdate extends AblyBaseCommand { static override flags = { ...productApiFlags, - ...clientIdFlag, + "client-id": Flags.string({ + description: 'ClientId of a channels presence member.', + required: true, + }), data: Flags.string({ description: "JSON data to associate with the presence update", - required: true, }), ...durationFlag, }; @@ -51,7 +53,7 @@ export default class ChannelsPresenceUpdate extends AblyBaseCommand { const client = this.client; const { channel: channelName } = args; - const data = this.parseJsonFlag(flags.data, "data", flags); + const data = this.parseJsonFlag(flags.data ?? "{}", "data", flags); this.channel = client.channels.get(channelName); diff --git a/src/commands/rooms/presence/get-all.ts b/src/commands/rooms/presence/get.ts similarity index 91% rename from src/commands/rooms/presence/get-all.ts rename to src/commands/rooms/presence/get.ts index de60bd07..13db96b4 100644 --- a/src/commands/rooms/presence/get-all.ts +++ b/src/commands/rooms/presence/get.ts @@ -24,7 +24,7 @@ import { // Chat SDK maps room presence to the underlying channel: roomName::$chat const chatChannelName = (roomName: string) => `${roomName}::$chat`; -export default class RoomsPresenceGetAll extends AblyBaseCommand { +export default class RoomsPresenceGet extends AblyBaseCommand { static override args = { room: Args.string({ description: "Room to get presence members for", @@ -36,10 +36,10 @@ export default class RoomsPresenceGetAll extends AblyBaseCommand { "Get all current presence members in a chat room"; static override 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", + "$ ably rooms presence get my-room", + "$ ably rooms presence get my-room --limit 50", + "$ ably rooms presence get my-room --json", + "$ ably rooms presence get my-room --pretty-json", ]; static override flags = { @@ -52,7 +52,7 @@ export default class RoomsPresenceGetAll extends AblyBaseCommand { }; async run(): Promise { - const { args, flags } = await this.parse(RoomsPresenceGetAll); + const { args, flags } = await this.parse(RoomsPresenceGet); try { const client = await this.createAblyRestClient(flags); @@ -158,7 +158,7 @@ export default class RoomsPresenceGetAll extends AblyBaseCommand { } } } catch (error) { - this.fail(error, flags, "roomPresenceGetAll", { + this.fail(error, flags, "roomPresenceGet", { room: args.room, }); } diff --git a/src/commands/rooms/presence/update.ts b/src/commands/rooms/presence/update.ts index 41502263..0b697752 100644 --- a/src/commands/rooms/presence/update.ts +++ b/src/commands/rooms/presence/update.ts @@ -10,6 +10,7 @@ import { formatProgress, formatResource, formatSuccess, + formatWarning, } from "../../../utils/output.js"; export default class RoomsPresenceUpdate extends ChatBaseCommand { @@ -31,10 +32,12 @@ export default class RoomsPresenceUpdate extends ChatBaseCommand { static override flags = { ...productApiFlags, - ...clientIdFlag, + "client-id": Flags.string({ + description: "ClientId of a rooms presence member.", + required: true, + }), data: Flags.string({ description: "JSON data to associate with the presence update", - required: true, }), ...durationFlag, }; @@ -58,11 +61,19 @@ export default class RoomsPresenceUpdate extends ChatBaseCommand { const { room: roomName } = args; const data = this.parseJsonFlag( - flags.data, + flags.data ?? "{}", "data", flags, ) as PresenceData; + const wildcardWarning = `Updating a clientId on behalf of another user using a wildcard (*) is not supported, since chat rooms only recognize explicitly identified clients. So, a member with provided clientId but a new connectionId will be entered and updated.`; + + if (this.shouldOutputJson(flags)) { + this.logJsonStatus("warning", wildcardWarning, flags); + } else { + this.log(formatWarning(wildcardWarning)); + } + this.setupConnectionStateLogging(this.chatClient.realtime, flags, { includeUserFriendlyMessages: true, }); @@ -91,7 +102,7 @@ export default class RoomsPresenceUpdate extends ChatBaseCommand { `Entering presence in room ${roomName}`, { room: roomName, clientId: this.chatClient.clientId }, ); - await this.room.presence.enter(data); + await this.room.presence.enter(); this.logCliEvent( flags, "presence", diff --git a/test/unit/commands/channels/presence/get-all.test.ts b/test/unit/commands/channels/presence/get.test.ts similarity index 87% rename from test/unit/commands/channels/presence/get-all.test.ts rename to test/unit/commands/channels/presence/get.test.ts index 60c08be4..a13dc7f5 100644 --- a/test/unit/commands/channels/presence/get-all.test.ts +++ b/test/unit/commands/channels/presence/get.test.ts @@ -31,7 +31,7 @@ const mockPresenceMembers = [ }, ]; -describe("channels:presence:get-all command", () => { +describe("channels:presence:get command", () => { beforeEach(() => { const mock = getMockAblyRest(); const channel = mock.channels._getChannel("test-channel"); @@ -41,11 +41,11 @@ describe("channels:presence:get-all command", () => { ); }); - standardHelpTests("channels:presence:get-all", import.meta.url); - standardArgValidationTests("channels:presence:get-all", import.meta.url, { + standardHelpTests("channels:presence:get", import.meta.url); + standardArgValidationTests("channels:presence:get", import.meta.url, { requiredArgs: ["test-channel"], }); - standardFlagTests("channels:presence:get-all", import.meta.url, [ + standardFlagTests("channels:presence:get", import.meta.url, [ "--limit", "--json", "--pretty-json", @@ -57,7 +57,7 @@ describe("channels:presence:get-all command", () => { const channel = mock.channels._getChannel("test-channel"); const { stdout } = await runCommand( - ["channels:presence:get-all", "test-channel"], + ["channels:presence:get", "test-channel"], import.meta.url, ); @@ -75,7 +75,7 @@ describe("channels:presence:get-all command", () => { channel.presence.get.mockResolvedValue(createMockPaginatedResult([])); const { stderr } = await runCommand( - ["channels:presence:get-all", "test-channel"], + ["channels:presence:get", "test-channel"], import.meta.url, ); @@ -84,7 +84,7 @@ describe("channels:presence:get-all command", () => { it("should output JSON with presenceMembers array", async () => { const { stdout } = await runCommand( - ["channels:presence:get-all", "test-channel", "--json"], + ["channels:presence:get", "test-channel", "--json"], import.meta.url, ); @@ -104,7 +104,7 @@ describe("channels:presence:get-all command", () => { it("should display member data when present", async () => { const { stdout } = await runCommand( - ["channels:presence:get-all", "test-channel"], + ["channels:presence:get", "test-channel"], import.meta.url, ); @@ -118,7 +118,7 @@ describe("channels:presence:get-all command", () => { const channel = mock.channels._getChannel("test-channel"); await runCommand( - ["channels:presence:get-all", "test-channel", "--limit", "50"], + ["channels:presence:get", "test-channel", "--limit", "50"], import.meta.url, ); @@ -144,7 +144,7 @@ describe("channels:presence:get-all command", () => { ); const { stdout } = await runCommand( - ["channels:presence:get-all", "test-channel", "--limit", "1", "--json"], + ["channels:presence:get", "test-channel", "--limit", "1", "--json"], import.meta.url, ); @@ -162,7 +162,7 @@ describe("channels:presence:get-all command", () => { channel.presence.get.mockRejectedValue(new Error("API error")); const { error } = await runCommand( - ["channels:presence:get-all", "test-channel"], + ["channels:presence:get", "test-channel"], import.meta.url, ); @@ -180,7 +180,7 @@ describe("channels:presence:get-all command", () => { ); const { error } = await runCommand( - ["channels:presence:get-all", "nonexistent-channel"], + ["channels:presence:get", "nonexistent-channel"], import.meta.url, ); diff --git a/test/unit/commands/channels/presence/update.test.ts b/test/unit/commands/channels/presence/update.test.ts index cdf7ebca..0f3e13fe 100644 --- a/test/unit/commands/channels/presence/update.test.ts +++ b/test/unit/commands/channels/presence/update.test.ts @@ -50,6 +50,8 @@ describe("channels:presence:update command", () => { [ "channels:presence:update", "test-channel", + "--client-id", + "test-client", "--data", '{"status":"away"}', ], @@ -72,6 +74,8 @@ describe("channels:presence:update command", () => { [ "channels:presence:update", "test-channel", + "--client-id", + "test-client", "--data", '{"status":"away"}', ], @@ -89,6 +93,8 @@ describe("channels:presence:update command", () => { [ "channels:presence:update", "test-channel", + "--client-id", + "test-client", "--data", '{"status":"away"}', "--json", @@ -114,6 +120,8 @@ describe("channels:presence:update command", () => { [ "channels:presence:update", "test-channel", + "--client-id", + "test-client", "--data", '{"status":"away"}', "--json", @@ -135,6 +143,8 @@ describe("channels:presence:update command", () => { [ "channels:presence:update", "test-channel", + "--client-id", + "test-client", "--data", "not-valid-json", ], @@ -158,6 +168,8 @@ describe("channels:presence:update command", () => { [ "channels:presence:update", "test-channel", + "--client-id", + "test-client", "--data", '{"status":"away"}', ], @@ -178,6 +190,8 @@ describe("channels:presence:update command", () => { [ "channels:presence:update", "test-channel", + "--client-id", + "test-client", "--data", '{"status":"away"}', ], diff --git a/test/unit/commands/rooms/presence/get-all.test.ts b/test/unit/commands/rooms/presence/get.test.ts similarity index 87% rename from test/unit/commands/rooms/presence/get-all.test.ts rename to test/unit/commands/rooms/presence/get.test.ts index 0a4b1a5c..3bda520c 100644 --- a/test/unit/commands/rooms/presence/get-all.test.ts +++ b/test/unit/commands/rooms/presence/get.test.ts @@ -32,7 +32,7 @@ const mockPresenceMembers = [ }, ]; -describe("rooms:presence:get-all command", () => { +describe("rooms:presence:get command", () => { beforeEach(() => { const mock = getMockAblyRest(); // Chat SDK maps room presence to roomName::$chat channel @@ -42,11 +42,11 @@ describe("rooms:presence:get-all command", () => { ); }); - standardHelpTests("rooms:presence:get-all", import.meta.url); - standardArgValidationTests("rooms:presence:get-all", import.meta.url, { + standardHelpTests("rooms:presence:get", import.meta.url); + standardArgValidationTests("rooms:presence:get", import.meta.url, { requiredArgs: ["test-room"], }); - standardFlagTests("rooms:presence:get-all", import.meta.url, [ + standardFlagTests("rooms:presence:get", import.meta.url, [ "--limit", "--json", "--pretty-json", @@ -58,7 +58,7 @@ describe("rooms:presence:get-all command", () => { const channel = mock.channels._getChannel("test-room::$chat"); const { stdout } = await runCommand( - ["rooms:presence:get-all", "test-room"], + ["rooms:presence:get", "test-room"], import.meta.url, ); @@ -77,7 +77,7 @@ describe("rooms:presence:get-all command", () => { createMockPaginatedResult(mockPresenceMembers), ); - await runCommand(["rooms:presence:get-all", "my-room"], import.meta.url); + await runCommand(["rooms:presence:get", "my-room"], import.meta.url); expect(channel.presence.get).toHaveBeenCalled(); }); @@ -88,7 +88,7 @@ describe("rooms:presence:get-all command", () => { channel.presence.get.mockResolvedValue(createMockPaginatedResult([])); const { stdout } = await runCommand( - ["rooms:presence:get-all", "test-room"], + ["rooms:presence:get", "test-room"], import.meta.url, ); @@ -97,7 +97,7 @@ describe("rooms:presence:get-all command", () => { it("should output JSON with presenceMembers array", async () => { const { stdout } = await runCommand( - ["rooms:presence:get-all", "test-room", "--json"], + ["rooms:presence:get", "test-room", "--json"], import.meta.url, ); @@ -119,7 +119,7 @@ describe("rooms:presence:get-all command", () => { it("should display member data and action when present", async () => { const { stdout } = await runCommand( - ["rooms:presence:get-all", "test-room"], + ["rooms:presence:get", "test-room"], import.meta.url, ); @@ -133,7 +133,7 @@ describe("rooms:presence:get-all command", () => { const channel = mock.channels._getChannel("test-room::$chat"); await runCommand( - ["rooms:presence:get-all", "test-room", "--limit", "50"], + ["rooms:presence:get", "test-room", "--limit", "50"], import.meta.url, ); @@ -158,7 +158,7 @@ describe("rooms:presence:get-all command", () => { ); const { stdout } = await runCommand( - ["rooms:presence:get-all", "test-room", "--limit", "1", "--json"], + ["rooms:presence:get", "test-room", "--limit", "1", "--json"], import.meta.url, ); @@ -176,7 +176,7 @@ describe("rooms:presence:get-all command", () => { channel.presence.get.mockRejectedValue(new Error("API error")); const { error } = await runCommand( - ["rooms:presence:get-all", "test-room"], + ["rooms:presence:get", "test-room"], import.meta.url, ); @@ -194,7 +194,7 @@ describe("rooms:presence:get-all command", () => { ); const { error } = await runCommand( - ["rooms:presence:get-all", "nonexistent"], + ["rooms:presence:get", "nonexistent"], import.meta.url, ); diff --git a/test/unit/commands/rooms/presence/update.test.ts b/test/unit/commands/rooms/presence/update.test.ts index f0877e9f..505a1e0f 100644 --- a/test/unit/commands/rooms/presence/update.test.ts +++ b/test/unit/commands/rooms/presence/update.test.ts @@ -30,7 +30,14 @@ describe("rooms:presence:update command", () => { const room = mock.rooms._getRoom("test-room"); const { stdout } = await runCommand( - ["rooms:presence:update", "test-room", "--data", '{"status":"away"}'], + [ + "rooms:presence:update", + "test-room", + "--client-id", + "test-client", + "--data", + '{"status":"away"}', + ], import.meta.url, ); @@ -38,13 +45,20 @@ describe("rooms:presence:update command", () => { expect(stdout).toContain("Updated"); expect(stdout).toContain("test-room"); expect(room.attach).toHaveBeenCalled(); - expect(room.presence.enter).toHaveBeenCalledWith({ status: "away" }); + expect(room.presence.enter).toHaveBeenCalled(); expect(room.presence.update).toHaveBeenCalledWith({ status: "away" }); }); it("should show labeled output in human mode", async () => { const { stdout } = await runCommand( - ["rooms:presence:update", "test-room", "--data", '{"status":"away"}'], + [ + "rooms:presence:update", + "test-room", + "--client-id", + "test-client", + "--data", + '{"status":"away"}', + ], import.meta.url, ); @@ -60,6 +74,8 @@ describe("rooms:presence:update command", () => { [ "rooms:presence:update", "test-room", + "--client-id", + "test-client", "--data", '{"status":"away"}', "--json", @@ -86,6 +102,8 @@ describe("rooms:presence:update command", () => { [ "rooms:presence:update", "test-room", + "--client-id", + "test-client", "--data", '{"status":"away"}', "--json", @@ -94,17 +112,26 @@ describe("rooms:presence:update command", () => { ); }); - const statusRecords = allRecords.filter((r) => r.type === "status"); - expect(statusRecords.length).toBeGreaterThanOrEqual(1); + const holdRecords = allRecords.filter( + (r) => r.type === "status" && r.status === "holding", + ); + expect(holdRecords.length).toBeGreaterThanOrEqual(1); - const status = statusRecords[0]; + const status = holdRecords[0]; expect(status.status).toBe("holding"); expect(status.message).toContain("Holding presence"); }); it("should handle invalid JSON data gracefully", async () => { const { error } = await runCommand( - ["rooms:presence:update", "test-room", "--data", "not-valid-json"], + [ + "rooms:presence:update", + "test-room", + "--client-id", + "test-client", + "--data", + "not-valid-json", + ], import.meta.url, ); @@ -117,7 +144,14 @@ describe("rooms:presence:update command", () => { const room = mock.rooms._getRoom("test-room"); await runCommand( - ["rooms:presence:update", "test-room", "--data", '{"status":"away"}'], + [ + "rooms:presence:update", + "test-room", + "--client-id", + "test-client", + "--data", + '{"status":"away"}', + ], import.meta.url, ); @@ -134,7 +168,14 @@ describe("rooms:presence:update command", () => { room.presence.enter.mockRejectedValue(new Error("Presence enter failed")); const { error } = await runCommand( - ["rooms:presence:update", "test-room", "--data", '{"status":"away"}'], + [ + "rooms:presence:update", + "test-room", + "--client-id", + "test-client", + "--data", + '{"status":"away"}', + ], import.meta.url, ); @@ -149,7 +190,14 @@ describe("rooms:presence:update command", () => { ); const { error } = await runCommand( - ["rooms:presence:update", "test-room", "--data", '{"status":"away"}'], + [ + "rooms:presence:update", + "test-room", + "--client-id", + "test-client", + "--data", + '{"status":"away"}', + ], import.meta.url, );