Prechádzať zdrojové kódy

chore(lazer-sdk-js): responded to further PR feedback about typing exhaustivity, using a utility function for buffer creation, as well as one for adding the token to the URL

benduran 1 mesiac pred
rodič
commit
7aebe1f1b2

+ 2 - 2
lazer/sdk/js/src/client.ts

@@ -19,7 +19,7 @@ import type {
 import { BINARY_UPDATE_FORMAT_MAGIC_LE, FORMAT_MAGICS_LE } from "./protocol.js";
 import type { WebSocketPoolConfig } from "./socket/websocket-pool.js";
 import { WebSocketPool } from "./socket/websocket-pool.js";
-import { IsomorphicBuffer } from "./util/index.js";
+import { bufferFromWebsocketData } from "./util/buffer-util.js";
 
 export type BinaryResponse = {
   subscriptionId: number;
@@ -121,7 +121,7 @@ export class PythLazerClient {
         });
         return;
       }
-      const buffData = await IsomorphicBuffer.fromWebsocketData(data);
+      const buffData = await bufferFromWebsocketData(data);
       let pos = 0;
       const magic = buffData
         .subarray(pos, pos + UINT32_NUM_BYTES)

+ 5 - 4
lazer/sdk/js/src/socket/resilient-websocket.ts

@@ -29,11 +29,12 @@ export type ResilientWebSocketConfig = {
  * and falling back to using the https://www.npmjs.com/package/ws package, this
  * means there are API differences between the native WebSocket (the one in a web browser)
  * and the server-side version from the "ws" package.
- * 
+ *
  * This type creates a WebSocket type reference we use to indicate the unknown
  * nature of the env in which is code is run.
  */
-type UnsafeWebSocket = (Omit<WebSocket, "terminate"> & Partial<Pick<WebSocket, "terminate">>);
+type UnsafeWebSocket = Omit<WebSocket, "terminate"> &
+  Partial<Pick<WebSocket, "terminate">>;
 
 export class ResilientWebSocket {
   private endpoint: string;
@@ -220,8 +221,8 @@ export class ResilientWebSocket {
     if (this.shouldLogRetry()) {
       this.logger.error(
         "Connection closed unexpectedly or because of timeout. Reconnecting after " +
-        String(this.retryDelayMs()) +
-        "ms.",
+          String(this.retryDelayMs()) +
+          "ms.",
       );
     }
 

+ 15 - 9
lazer/sdk/js/src/socket/websocket-pool.ts

@@ -11,7 +11,11 @@ import {
   DEFAULT_STREAM_SERVICE_0_URL,
   DEFAULT_STREAM_SERVICE_1_URL,
 } from "../constants.js";
-import { addAuthTokenToWebSocketUrl, envIsBrowserOrWorker, IsomorphicBuffer } from "../util/index.js";
+import {
+  addAuthTokenToWebSocketUrl,
+  bufferFromWebsocketData,
+  envIsBrowserOrWorker,
+} from "../util/index.js";
 
 const DEFAULT_NUM_CONNECTIONS = 4;
 
@@ -72,7 +76,9 @@ export class WebSocketPool {
     for (let i = 0; i < numConnections; i++) {
       const baseUrl = urls[i % urls.length];
       const isBrowser = envIsBrowserOrWorker();
-      const url = isBrowser ? addAuthTokenToWebSocketUrl(baseUrl, token) : baseUrl;
+      const url = isBrowser
+        ? addAuthTokenToWebSocketUrl(baseUrl, token)
+        : baseUrl;
       if (!url) {
         throw new Error(`URLs must not be null or empty`);
       }
@@ -151,18 +157,18 @@ export class WebSocketPool {
     }
   }
 
+  private async constructCacheKeyFromWebsocketData(data: WebSocket.Data) {
+    if (typeof data === "string") return data;
+    const buff = await bufferFromWebsocketData(data);
+    return buff.toString("hex");
+  }
+
   /**
    * Handles incoming websocket messages by deduplicating identical messages received across
    * multiple connections before forwarding to registered handlers
    */
   dedupeHandler = async (data: WebSocket.Data): Promise<void> => {
-    let cacheKey = "";
-    if (typeof data === "string") {
-      cacheKey = data;
-    } else {
-      const buff = await IsomorphicBuffer.fromWebsocketData(data);
-      cacheKey = buff.toString("hex");
-    }
+    const cacheKey = await this.constructCacheKeyFromWebsocketData(data);
 
     if (this.cache.has(cacheKey)) {
       this.logger.debug("Dropping duplicate message");

+ 28 - 32
lazer/sdk/js/src/util/buffer-util.ts

@@ -6,37 +6,33 @@ import { Buffer as BrowserBuffer } from "buffer";
 
 import type { Data } from "isomorphic-ws";
 
-const { Buffer: PossibleBuiltInBuffer } = globalThis as Partial<{
-  Buffer: typeof Buffer;
-}>;
-
-const BufferClassToUse = PossibleBuiltInBuffer ?? BrowserBuffer;
-
-export class IsomorphicBuffer extends BufferClassToUse {
-  /**
-   * given a relatively unknown websocket frame data object,
-   * returns a valid Buffer instance that is safe to use
-   * isomorphically in any JS runtime environment
-   */
-  static async fromWebsocketData(data: Data) {
-    if (typeof data === "string") {
-      return BufferClassToUse.from(new TextEncoder().encode(data).buffer);
-    }
-    if (data instanceof Blob) {
-      // let the uncaught promise exception bubble up if there's an issue
-      return BufferClassToUse.from(await data.arrayBuffer());
-    }
-    if (data instanceof ArrayBuffer) return BufferClassToUse.from(data);
-    if (Buffer.isBuffer(data)) {
-      const arrBuffer = new ArrayBuffer(data.length);
-      const v = new Uint8Array(arrBuffer);
-      for (const [i, item] of data.entries()) {
-        v[i] = item;
-      }
-      return BufferClassToUse.from(arrBuffer);
-    }
-    throw new TypeError(
-      "unexpected event data type found when IsomorphicBuffer.fromWebsocketData() called",
-    );
+const BufferClassToUse =
+  "Buffer" in globalThis ? globalThis.Buffer : BrowserBuffer;
+
+/**
+ * given a relatively unknown websocket frame data object,
+ * returns a valid Buffer instance that is safe to use
+ * isomorphically in any JS runtime environment
+ */
+export async function bufferFromWebsocketData(data: Data): Promise<Buffer> {
+  if (typeof data === "string") {
+    return BufferClassToUse.from(new TextEncoder().encode(data).buffer);
+  }
+
+  if (data instanceof BufferClassToUse) return data;
+
+  if (data instanceof Blob) {
+    // let the uncaught promise exception bubble up if there's an issue
+    return BufferClassToUse.from(await data.arrayBuffer());
   }
+
+  if (data instanceof ArrayBuffer) return BufferClassToUse.from(data);
+
+  if (Array.isArray(data)) {
+    // an array of buffers is highly unlikely, but it is a possibility
+    // indicated by the WebSocket Data interface
+    return BufferClassToUse.concat(data);
+  }
+
+  return data;
 }

+ 1 - 1
lazer/sdk/js/src/util/index.ts

@@ -1,3 +1,3 @@
 export * from "./buffer-util.js";
 export * from "./env-util.js";
-export * from "./url-util.js";
+export * from "./url-util.js";

+ 6 - 3
lazer/sdk/js/src/util/url-util.ts

@@ -1,4 +1,4 @@
-const ACCESS_TOKEN_QUERY_PARAM_KEY = 'ACCESS_TOKEN';
+const ACCESS_TOKEN_QUERY_PARAM_KEY = "ACCESS_TOKEN";
 
 /**
  * Given a URL to a hosted lazer stream service and a possible auth token,
@@ -7,10 +7,13 @@ const ACCESS_TOKEN_QUERY_PARAM_KEY = 'ACCESS_TOKEN';
  * If the URL provided is nullish, it is returned as-is (in the same nullish format).
  * If the token is nullish, the baseUrl given is returned, instead.
  */
-export function addAuthTokenToWebSocketUrl(baseUrl: string | null | undefined, authToken: string | null | undefined) {
+export function addAuthTokenToWebSocketUrl(
+  baseUrl: string | null | undefined,
+  authToken: string | null | undefined,
+) {
   if (!baseUrl || !authToken) return baseUrl;
   const parsedUrl = new URL(baseUrl);
   parsedUrl.searchParams.set(ACCESS_TOKEN_QUERY_PARAM_KEY, authToken);
 
   return parsedUrl.toString();
-}
+}