Skip to content

Commit

Permalink
migrate websocket to pure TS
Browse files Browse the repository at this point in the history
  • Loading branch information
lostbean committed Jan 4, 2024
1 parent 423d80b commit ea77156
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 117 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
KURTOSIS_DEFAULT_REST_API_PORT,
} from "../constants";
import { KurtosisClient } from "./KurtosisClient";
import createWSClient from "./websocketClient/WebSocketClient";
import { createWSClient } from "./websocketClient/WebSocketClient";

function constructGatewayURL(remoteHost: string): string {
return `${KURTOSIS_CLOUD_UI_URL}/gateway/ips/${remoteHost}/ports/${KURTOSIS_DEFAULT_EM_API_PORT}`;
Expand All @@ -22,7 +22,7 @@ function constructRESTGatewayURL(remoteHost: string): string {
}

function constructWSGatewayURL(remoteHost: string): string {
return constructRESTGatewayURL(remoteHost).replace(/^http/, 'ws');
return constructRESTGatewayURL(remoteHost).replace(/^http/, "ws");
}

export class AuthenticatedKurtosisClient extends KurtosisClient {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { components, paths } from "kurtosis-sdk/src/engine/rest_api_bindings/typ
import { assertDefined, asyncResult, isDefined, RemoveFunctions } from "kurtosis-ui-components";
import createClient from "openapi-fetch";
import { EnclaveFullInfo } from "../../emui/enclaves/types";
import createWSClient from "./websocketClient/WebSocketClient";
import { createWSClient } from "./websocketClient/WebSocketClient";

type KurtosisRestClient = ReturnType<typeof createClient<paths>>;
type KurtosisWebsocketClient = ReturnType<typeof createWSClient<paths>>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@ import { createConnectTransport } from "@connectrpc/connect-web";
import { KurtosisEnclaveManagerServer } from "enclave-manager-sdk/build/kurtosis_enclave_manager_api_connect";
import { paths } from "kurtosis-sdk/src/engine/rest_api_bindings/types";
import createClient from "openapi-fetch";
import { KURTOSIS_EM_API_DEFAULT_URL, KURTOSIS_REST_API_DEFAULT_URL, KURTOSIS_WEBSOCKET_API_DEFAULT_URL } from "../constants";
import {
KURTOSIS_EM_API_DEFAULT_URL,
KURTOSIS_REST_API_DEFAULT_URL,
KURTOSIS_WEBSOCKET_API_DEFAULT_URL,
} from "../constants";
import { KurtosisClient } from "./KurtosisClient";
import createWSClient from "./websocketClient/WebSocketClient";
import { createWSClient } from "./websocketClient/WebSocketClient";

export class LocalKurtosisClient extends KurtosisClient {
constructor() {
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import type {
ErrorResponse,
FilterKeys,
MediaType,
PathsWithMethod,
ResponseObjectMap,
SuccessResponse,
} from "openapi-typescript-helpers";

import type { ParamsOption, QuerySerializer } from "openapi-fetch";

import { createFinalURL, defaultQuerySerializer } from "openapi-fetch";

export interface ClientOptions {
baseUrl?: string;
}

export type RequestOptions<T> = ParamsOption<T> & {
querySerializer?: QuerySerializer<T>;
abortSignal?: AbortSignal;
};

type ReturnType<Paths extends {}, P extends PathsWithMethod<Paths, "get">> = "get" extends infer T
? T extends "get"
? T extends keyof Paths[P]
? Paths[P][T]
: unknown
: never
: never;

type ParamsType<Paths extends {}, P extends PathsWithMethod<Paths, "get">> = RequestOptions<
FilterKeys<Paths[P], "get">
>;

export type MessageResponse<T> =
| {
data: FilterKeys<SuccessResponse<ResponseObjectMap<T>>, MediaType>;
error?: never;
message: MessageEvent<any>;
}
| {
data?: never;
error: FilterKeys<ErrorResponse<ResponseObjectMap<T>>, MediaType>;
message: MessageEvent<any>;
};

// This implementation is based on the http version of the lib `openapi-fetch`
// https://github.com/drwpow/openapi-typescript/blob/main/packages/openapi-fetch/src/index.d.ts
export function createWSClient<Paths extends {}>(
clientOptions?: ClientOptions,
): {
WS: <P extends PathsWithMethod<Paths, "get">, T extends keyof Paths[P]>(
url: P,
...init: ParamsType<Paths, P>[]
) => AsyncGenerator<MessageResponse<ReturnType<Paths, P>>>;
} {
var baseUrl = clientOptions?.baseUrl ?? "";
if (baseUrl.endsWith("/")) {
baseUrl = baseUrl.slice(0, -1); // remove trailing slash
}

return {
/** Call a WS endpoint */
WS: async function* (url, fetchOptions) {
const { params = {}, querySerializer = defaultQuerySerializer, abortSignal, ...init } = fetchOptions || {};

// build full URL
const finalURL = createFinalURL(url.toString(), {
baseUrl,
params,
querySerializer,
});

var socket: WebSocket;
try {
socket = new WebSocket(finalURL);
} catch (error) {
return { error: {}, data: null };
}

// Wait for the WebSocket connection to be open
await new Promise((resolve) => {
socket.addEventListener("open", resolve);
});

if (abortSignal) {
// already aborted, fail immediately
if (abortSignal.aborted) {
console.warn(`Websocket on ${finalURL} got aborted before using. Closing it.`);
socket.close();
}

// close later if aborted
abortSignal.addEventListener("abort", () => {
console.warn(`Websocket on ${finalURL} has been asked to abort. Closing it.`);
socket.close();
});
}

try {
while (socket.readyState === WebSocket.OPEN) {
// Wait for the next message
const message: MessageEvent<any> = await new Promise((resolve) => {
socket.addEventListener("message", (event: MessageEvent<any>) => resolve(event));
});

// Yield the received message
yield { error: undefined, data: JSON.parse(message.data), message: message };
}
} catch (error) {
console.error(`Received an unexpected message from the channel on ${finalURL}:`);
console.error(error);
} finally {
// Close the WebSocket connection when the generator is done
socket.close();
}
},
};
}

0 comments on commit ea77156

Please sign in to comment.