Skip to content

Commit 3eb8e1a

Browse files
committed
Move success messages after await subscribe and add error hints for disabled apps and disconnections
1 parent 3fe330a commit 3eb8e1a

11 files changed

Lines changed: 195 additions & 20 deletions

File tree

src/commands/logs/channel-lifecycle/subscribe.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -79,14 +79,6 @@ export default class LogsChannelLifecycleSubscribe extends AblyBaseCommand {
7979
"subscribing",
8080
`Subscribing to ${channelName}...`,
8181
);
82-
if (!this.shouldOutputJson(flags)) {
83-
this.log(
84-
formatSuccess(`Subscribed to ${formatResource(channelName)}.`),
85-
);
86-
this.log(formatListening("Listening for channel lifecycle logs."));
87-
this.log("");
88-
}
89-
9082
// Subscribe to the channel
9183
await channel.subscribe((message) => {
9284
const timestamp = formatMessageTimestamp(message.timestamp);
@@ -136,6 +128,15 @@ export default class LogsChannelLifecycleSubscribe extends AblyBaseCommand {
136128

137129
this.log(""); // Empty line for better readability
138130
});
131+
132+
if (!this.shouldOutputJson(flags)) {
133+
this.log(
134+
formatSuccess(`Subscribed to ${formatResource(channelName)}.`),
135+
);
136+
this.log(formatListening("Listening for channel lifecycle logs."));
137+
this.log("");
138+
}
139+
139140
this.logCliEvent(
140141
flags,
141142
"logs",

src/commands/logs/connection-lifecycle/subscribe.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,6 @@ export default class LogsConnectionLifecycleSubscribe extends AblyBaseCommand {
8080
{ channel: logsChannelName },
8181
);
8282

83-
if (!this.shouldOutputJson(flags)) {
84-
this.log(formatSuccess("Subscribed to connection lifecycle logs."));
85-
}
86-
8783
// Subscribe to connection lifecycle logs
8884
await channel.subscribe((message: Ably.Message) => {
8985
const timestamp = formatMessageTimestamp(message.timestamp);
@@ -118,6 +114,10 @@ export default class LogsConnectionLifecycleSubscribe extends AblyBaseCommand {
118114
}
119115
});
120116

117+
if (!this.shouldOutputJson(flags)) {
118+
this.log(formatSuccess("Subscribed to connection lifecycle logs."));
119+
}
120+
121121
this.logCliEvent(
122122
flags,
123123
"logs",

src/commands/logs/subscribe.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -111,14 +111,6 @@ export default class LogsSubscribe extends AblyBaseCommand {
111111
{ logTypes, channel: logsChannelName },
112112
);
113113

114-
if (!this.shouldOutputJson(flags)) {
115-
this.log(
116-
formatSuccess(
117-
`Subscribed to app logs: ${formatResource(logTypes.join(", "))}.`,
118-
),
119-
);
120-
}
121-
122114
// Subscribe to specified log types
123115
const subscribePromises: Promise<unknown>[] = [];
124116
for (const logType of logTypes) {
@@ -160,6 +152,14 @@ export default class LogsSubscribe extends AblyBaseCommand {
160152

161153
await Promise.all(subscribePromises);
162154

155+
if (!this.shouldOutputJson(flags)) {
156+
this.log(
157+
formatSuccess(
158+
`Subscribed to app logs: ${formatResource(logTypes.join(", "))}.`,
159+
),
160+
);
161+
}
162+
163163
this.logCliEvent(
164164
flags,
165165
"logs",

src/utils/errors.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ const hints: Record<number, string> = {
2121
'Run "ably auth keys list" to check your key\'s publish capability, or update it in the Ably dashboard.',
2222
40171:
2323
'Run "ably auth keys list" to check your key\'s capabilities, or update them in the Ably dashboard.',
24+
40300:
25+
"This application has been disabled. Check the app status in the Ably dashboard at https://ably.com/dashboard",
26+
80003:
27+
"The connection was lost. Check your network connection and try again.",
2428
};
2529

2630
export function getFriendlyAblyErrorHint(code?: number): string | undefined {

test/unit/commands/channels/presence/enter.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,5 +171,35 @@ describe("channels:presence:enter command", () => {
171171

172172
expect(error).toBeDefined();
173173
});
174+
175+
it("should handle capability error with --show-others gracefully", async () => {
176+
const mock = getMockAblyRealtime();
177+
const channel = mock.channels._getChannel("test-channel");
178+
179+
channel.presence.subscribe.mockRejectedValue(
180+
Object.assign(
181+
new Error("Channel denied access based on given capability"),
182+
{
183+
code: 40160,
184+
statusCode: 401,
185+
href: "https://help.ably.io/error/40160",
186+
},
187+
),
188+
);
189+
190+
const { error } = await runCommand(
191+
[
192+
"channels:presence:enter",
193+
"test-channel",
194+
"--client-id",
195+
"test-client",
196+
"--show-others",
197+
],
198+
import.meta.url,
199+
);
200+
201+
expect(error).toBeDefined();
202+
expect(error?.message).toContain("Channel denied access");
203+
});
174204
});
175205
});

test/unit/commands/channels/subscribe.test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,5 +265,41 @@ describe("channels:subscribe command", () => {
265265
expect(error?.message).toContain("capability");
266266
expect(error?.message).toContain("Ably dashboard");
267267
});
268+
269+
it("should include hint in JSON error output for capability errors", async () => {
270+
const mock = getMockAblyRealtime();
271+
const channel = mock.channels._getChannel("test-channel");
272+
273+
channel.subscribe.mockRejectedValue(
274+
Object.assign(
275+
new Error("Channel denied access based on given capability"),
276+
{
277+
code: 40160,
278+
statusCode: 401,
279+
href: "https://help.ably.io/error/40160",
280+
},
281+
),
282+
);
283+
284+
const { error, stdout } = await runCommand(
285+
["channels:subscribe", "test-channel", "--json"],
286+
import.meta.url,
287+
);
288+
289+
expect(error).toBeDefined();
290+
const jsonLines = stdout
291+
.split("\n")
292+
.filter((l: string) => l.trim().startsWith("{"));
293+
const errorLine = jsonLines.find((l: string) =>
294+
l.includes('"type":"error"'),
295+
);
296+
expect(errorLine).toBeDefined();
297+
const parsed = JSON.parse(errorLine!);
298+
expect(parsed.type).toBe("error");
299+
expect(parsed.success).toBe(false);
300+
expect(parsed.code).toBe(40160);
301+
expect(parsed.hint).toBeDefined();
302+
expect(parsed.hint).toContain("Ably dashboard");
303+
});
268304
});
269305
});

test/unit/commands/logs/channel-lifecycle/subscribe.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,5 +138,29 @@ describe("logs:channel-lifecycle:subscribe command", () => {
138138
expect(error).toBeDefined();
139139
expect(error?.message).toMatch(/No mock|client/i);
140140
});
141+
142+
it("should handle capability error gracefully", async () => {
143+
const mock = getMockAblyRealtime();
144+
const channel = mock.channels._getChannel("[meta]channel.lifecycle");
145+
146+
channel.subscribe.mockRejectedValue(
147+
Object.assign(
148+
new Error("Channel denied access based on given capability"),
149+
{
150+
code: 40160,
151+
statusCode: 401,
152+
href: "https://help.ably.io/error/40160",
153+
},
154+
),
155+
);
156+
157+
const { error } = await runCommand(
158+
["logs:channel-lifecycle:subscribe"],
159+
import.meta.url,
160+
);
161+
162+
expect(error).toBeDefined();
163+
expect(error?.message).toContain("Channel denied access");
164+
});
141165
});
142166
});

test/unit/commands/logs/connection-lifecycle/subscribe.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,5 +356,29 @@ describe("LogsConnectionLifecycleSubscribe", function () {
356356
expect(error).toBeDefined();
357357
expect(error?.message).toContain("Channel subscribe failed");
358358
});
359+
360+
it("should handle capability error gracefully", async () => {
361+
const mock = getMockAblyRealtime();
362+
const channel = mock.channels._getChannel("[meta]connection.lifecycle");
363+
364+
channel.subscribe.mockRejectedValue(
365+
Object.assign(
366+
new Error("Channel denied access based on given capability"),
367+
{
368+
code: 40160,
369+
statusCode: 401,
370+
href: "https://help.ably.io/error/40160",
371+
},
372+
),
373+
);
374+
375+
const { error } = await runCommand(
376+
["logs:connection-lifecycle:subscribe"],
377+
import.meta.url,
378+
);
379+
380+
expect(error).toBeDefined();
381+
expect(error?.message).toContain("Channel denied access");
382+
});
359383
});
360384
});

test/unit/commands/logs/push/subscribe.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,30 @@ describe("logs:push:subscribe command", () => {
5252
expect(error).toBeDefined();
5353
expect(error?.message).toMatch(/No mock|client/i);
5454
});
55+
56+
it("should handle capability error gracefully", async () => {
57+
const mock = getMockAblyRealtime();
58+
const channel = mock.channels._getChannel("[meta]log:push");
59+
60+
channel.subscribe.mockRejectedValue(
61+
Object.assign(
62+
new Error("Channel denied access based on given capability"),
63+
{
64+
code: 40160,
65+
statusCode: 401,
66+
href: "https://help.ably.io/error/40160",
67+
},
68+
),
69+
);
70+
71+
const { error } = await runCommand(
72+
["logs:push:subscribe"],
73+
import.meta.url,
74+
);
75+
76+
expect(error).toBeDefined();
77+
expect(error?.message).toContain("Channel denied access");
78+
});
5579
});
5680

5781
describe("functionality", () => {

test/unit/commands/logs/subscribe.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,5 +193,26 @@ describe("logs:subscribe command", () => {
193193
expect(error).toBeDefined();
194194
expect(error?.message).toMatch(/No mock|client/i);
195195
});
196+
197+
it("should handle capability error gracefully", async () => {
198+
const mock = getMockAblyRealtime();
199+
const channel = mock.channels._getChannel("[meta]log");
200+
201+
channel.subscribe.mockRejectedValue(
202+
Object.assign(
203+
new Error("Channel denied access based on given capability"),
204+
{
205+
code: 40160,
206+
statusCode: 401,
207+
href: "https://help.ably.io/error/40160",
208+
},
209+
),
210+
);
211+
212+
const { error } = await runCommand(["logs:subscribe"], import.meta.url);
213+
214+
expect(error).toBeDefined();
215+
expect(error?.message).toContain("Channel denied access");
216+
});
196217
});
197218
});

0 commit comments

Comments
 (0)