Skip to content

Commit 92ab411

Browse files
committed
Review comments
1 parent f08f519 commit 92ab411

File tree

4 files changed

+45
-47
lines changed

4 files changed

+45
-47
lines changed

src/api/coderApi.ts

Lines changed: 27 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -448,7 +448,7 @@ export class CoderApi extends Api implements vscode.Disposable {
448448
socketFactory,
449449
this.output,
450450
{
451-
onCertificateExpired: async () => {
451+
onCertificateRefreshNeeded: async () => {
452452
const refreshCommand = getRefreshCommand();
453453
if (!refreshCommand) {
454454
return false;
@@ -500,35 +500,33 @@ function setupInterceptors(client: CoderApi, output: Logger): void {
500500
async (err: unknown) => {
501501
const refreshCommand = getRefreshCommand();
502502
const certError = ClientCertificateError.fromError(err);
503-
if (certError) {
504-
if (certError.isRefreshable && refreshCommand) {
505-
const config = (
506-
err as {
507-
config?: RequestConfigWithMeta & {
508-
_certRetried?: boolean;
509-
};
510-
}
511-
).config;
512-
513-
if (config && !config._certRetried) {
514-
config._certRetried = true;
515-
516-
output.info(
517-
`Client certificate error (alert ${certError.alertCode}), attempting refresh...`,
503+
if (certError && certError.isRefreshable && refreshCommand) {
504+
const config = (
505+
err as {
506+
config?: RequestConfigWithMeta & {
507+
_certRetried?: boolean;
508+
};
509+
}
510+
).config;
511+
512+
if (config && !config._certRetried) {
513+
config._certRetried = true;
514+
515+
output.info(
516+
`Client certificate error (alert ${certError.alertCode}), attempting refresh...`,
517+
);
518+
const success = await refreshCertificates(refreshCommand, output);
519+
if (success) {
520+
// Create new agent with refreshed certificates.
521+
const agent = await createHttpAgent(
522+
vscode.workspace.getConfiguration(),
518523
);
519-
const success = await refreshCertificates(refreshCommand, output);
520-
if (success) {
521-
// Create new agent with refreshed certificates.
522-
const agent = await createHttpAgent(
523-
vscode.workspace.getConfiguration(),
524-
);
525-
config.httpsAgent = agent;
526-
config.httpAgent = agent;
527-
528-
// Retry the request.
529-
output.info("Retrying request with refreshed certificates...");
530-
return client.getAxiosInstance().request(config);
531-
}
524+
config.httpsAgent = agent;
525+
config.httpAgent = agent;
526+
527+
// Retry the request.
528+
output.info("Retrying request with refreshed certificates...");
529+
return client.getAxiosInstance().request(config);
532530
}
533531
}
534532

src/error/clientCertificateError.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,21 @@ export const CLIENT_CERT_MESSAGES: Record<CLIENT_CERT_ALERT, string> = {
3636
"Access was denied using your TLS client certificate.",
3737
};
3838

39+
/**
40+
* Type guard to filter out reverse mappings from TypeScript numeric enums.
41+
*/
42+
function isNumericEnumEntry(
43+
entry: [string, string | CLIENT_CERT_ALERT],
44+
): entry is [string, CLIENT_CERT_ALERT] {
45+
return typeof entry[1] === "number";
46+
}
47+
3948
/**
4049
* Patterns to match SSL alert types in error messages (case-insensitive).
4150
*/
4251
const ALERT_PATTERNS: ReadonlyArray<[string, CLIENT_CERT_ALERT]> =
4352
Object.entries(CLIENT_CERT_ALERT)
44-
.filter(
45-
(entry): entry is [string, CLIENT_CERT_ALERT] =>
46-
typeof entry[1] === "number",
47-
)
53+
.filter(isNumericEnumEntry)
4854
.map(([name, code]) => [name.toLowerCase(), code]);
4955

5056
/**

src/websocket/reconnectingWebSocket.ts

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,15 @@ export interface ReconnectingWebSocketOptions {
2424
maxBackoffMs?: number;
2525
jitterFactor?: number;
2626
/** Callback invoked when a refreshable certificate error is detected. Returns true if refresh succeeded. */
27-
onCertificateExpired?: () => Promise<boolean>;
27+
onCertificateRefreshNeeded: () => Promise<boolean>;
2828
}
2929

3030
export class ReconnectingWebSocket<
3131
TData = unknown,
3232
> implements UnidirectionalStream<TData> {
3333
readonly #socketFactory: SocketFactory<TData>;
3434
readonly #logger: Logger;
35-
readonly #options: Required<
36-
Omit<ReconnectingWebSocketOptions, "onCertificateExpired">
37-
> &
38-
Pick<ReconnectingWebSocketOptions, "onCertificateExpired">;
35+
readonly #options: Required<ReconnectingWebSocketOptions>;
3936
readonly #eventHandlers: {
4037
[K in WebSocketEventType]: Set<EventHandler<TData, K>>;
4138
} = {
@@ -59,7 +56,7 @@ export class ReconnectingWebSocket<
5956
private constructor(
6057
socketFactory: SocketFactory<TData>,
6158
logger: Logger,
62-
options: ReconnectingWebSocketOptions = {},
59+
options: ReconnectingWebSocketOptions,
6360
onDispose?: () => void,
6461
) {
6562
this.#socketFactory = socketFactory;
@@ -68,7 +65,7 @@ export class ReconnectingWebSocket<
6865
initialBackoffMs: options.initialBackoffMs ?? 250,
6966
maxBackoffMs: options.maxBackoffMs ?? 30000,
7067
jitterFactor: options.jitterFactor ?? 0.1,
71-
onCertificateExpired: options.onCertificateExpired,
68+
onCertificateRefreshNeeded: options.onCertificateRefreshNeeded,
7269
};
7370
this.#backoffMs = this.#options.initialBackoffMs;
7471
this.#onDispose = onDispose;
@@ -77,7 +74,7 @@ export class ReconnectingWebSocket<
7774
static async create<TData>(
7875
socketFactory: SocketFactory<TData>,
7976
logger: Logger,
80-
options: ReconnectingWebSocketOptions = {},
77+
options: ReconnectingWebSocketOptions,
8178
onDispose?: () => void,
8279
): Promise<ReconnectingWebSocket<TData>> {
8380
const instance = new ReconnectingWebSocket<TData>(
@@ -296,14 +293,10 @@ export class ReconnectingWebSocket<
296293

297294
/**
298295
* Attempt to refresh certificates and return true if refresh succeeded.
299-
* Returns false if no callback configured or refresh failed.
300296
*/
301297
private async attemptCertificateRefresh(): Promise<boolean> {
302-
if (!this.#options.onCertificateExpired) {
303-
return false;
304-
}
305298
try {
306-
return await this.#options.onCertificateExpired();
299+
return await this.#options.onCertificateRefreshNeeded();
307300
} catch (refreshError) {
308301
this.#logger.error("Error during certificate refresh:", refreshError);
309302
return false;

test/unit/websocket/reconnectingWebSocket.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ describe("ReconnectingWebSocket", () => {
9494
const ws = await ReconnectingWebSocket.create(
9595
factory,
9696
createMockLogger(),
97+
{ onCertificateRefreshNeeded: () => Promise.resolve(false) },
9798
);
9899

99100
// Should be disconnected after unrecoverable HTTP error
@@ -627,7 +628,7 @@ async function fromFactory<T>(
627628
return await ReconnectingWebSocket.create(
628629
factory,
629630
createMockLogger(),
630-
undefined,
631+
{ onCertificateRefreshNeeded: () => Promise.resolve(false) },
631632
onDispose,
632633
);
633634
}

0 commit comments

Comments
 (0)