Add biome and autofix

This commit is contained in:
Endeavorance 2025-01-07 15:00:22 -05:00
parent 0036079c75
commit ba73b0a1f7
10 changed files with 350 additions and 318 deletions

31
biome.json Normal file
View file

@ -0,0 +1,31 @@
{
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
"vcs": {
"enabled": false,
"clientKind": "git",
"useIgnoreFile": true
},
"files": {
"ignoreUnknown": false,
"ignore": ["dist", ".next", "public", "*.d.ts", "*.json"]
},
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2
},
"organizeImports": {
"enabled": true
},
"linter": {
"enabled": true,
"rules": {
"recommended": true
}
},
"javascript": {
"formatter": {
"quoteStyle": "double"
}
}
}

View file

@ -1,8 +1,8 @@
import dts from "bun-plugin-dts"; import dts from "bun-plugin-dts";
await Bun.build({ await Bun.build({
entrypoints: ["./src/index.ts"], entrypoints: ["./src/index.ts"],
outdir: "./dist", outdir: "./dist",
plugins: [dts()], plugins: [dts()],
target: "browser", target: "browser",
}); });

BIN
bun.lockb

Binary file not shown.

View file

@ -15,6 +15,7 @@
], ],
"type": "module", "type": "module",
"devDependencies": { "devDependencies": {
"@biomejs/biome": "^1.9.4",
"@types/bun": "latest", "@types/bun": "latest",
"bun-plugin-dts": "^0.2.3" "bun-plugin-dts": "^0.2.3"
}, },

View file

@ -1,31 +1,31 @@
export enum SocketDisconnectCode { export enum SocketDisconnectCode {
CLOSE_NORMAL = 1000, CLOSE_NORMAL = 1000,
CLOSE_GOING_AWAY = 1001, CLOSE_GOING_AWAY = 1001,
CLOSE_PROTOCOL_ERROR = 1002, CLOSE_PROTOCOL_ERROR = 1002,
CLOSE_UNSUPPORTED = 1003, CLOSE_UNSUPPORTED = 1003,
CLOSE_NO_STATUS = 1005, CLOSE_NO_STATUS = 1005,
CLOSE_ABNORMAL = 1006, CLOSE_ABNORMAL = 1006,
CLOSE_TOO_LARGE = 1009, CLOSE_TOO_LARGE = 1009,
CLOSE_EXTENSION_REQUIRED = 1010, CLOSE_EXTENSION_REQUIRED = 1010,
CLOSE_INTERNAL_ERROR = 1011, CLOSE_INTERNAL_ERROR = 1011,
CLOSE_SERVICE_RESTART = 1012, CLOSE_SERVICE_RESTART = 1012,
CLOSE_TRY_AGAIN_LATER = 1013, CLOSE_TRY_AGAIN_LATER = 1013,
CLOSE_TLS_HANDSHAKE = 1015, CLOSE_TLS_HANDSHAKE = 1015,
} }
export const NON_RECONNECT_CODES = [ export const NON_RECONNECT_CODES = [
SocketDisconnectCode.CLOSE_NORMAL, SocketDisconnectCode.CLOSE_NORMAL,
SocketDisconnectCode.CLOSE_GOING_AWAY, SocketDisconnectCode.CLOSE_GOING_AWAY,
SocketDisconnectCode.CLOSE_UNSUPPORTED, SocketDisconnectCode.CLOSE_UNSUPPORTED,
SocketDisconnectCode.CLOSE_TOO_LARGE, SocketDisconnectCode.CLOSE_TOO_LARGE,
SocketDisconnectCode.CLOSE_EXTENSION_REQUIRED, SocketDisconnectCode.CLOSE_EXTENSION_REQUIRED,
SocketDisconnectCode.CLOSE_TRY_AGAIN_LATER, SocketDisconnectCode.CLOSE_TRY_AGAIN_LATER,
SocketDisconnectCode.CLOSE_TLS_HANDSHAKE, SocketDisconnectCode.CLOSE_TLS_HANDSHAKE,
] as const; ] as const;
export enum ManagedSocketErrorType { export enum ManagedSocketErrorType {
INVALID_MESSAGE_SHAPE = "Invalid message shape.", INVALID_MESSAGE_SHAPE = "Invalid message shape.",
CATOSTROPHIC_CLOSE = "Catostrophic close code", CATOSTROPHIC_CLOSE = "Catostrophic close code",
SOCKET_ERROR = "WebSocket error", SOCKET_ERROR = "WebSocket error",
CONNECTION_REJECTED = "Connection rejected", CONNECTION_REJECTED = "Connection rejected",
} }

View file

@ -1,17 +1,17 @@
import { ManagedSocketErrorType } from "./constants"; import { ManagedSocketErrorType } from "./constants";
export class ManagedSocketError extends Error { export class ManagedSocketError extends Error {
public originalError: Error | ErrorEvent; public originalError: Error | ErrorEvent;
public type: ManagedSocketErrorType = ManagedSocketErrorType.SOCKET_ERROR; public type: ManagedSocketErrorType = ManagedSocketErrorType.SOCKET_ERROR;
constructor( constructor(
message: string, message: string,
originalError: Error | ErrorEvent, originalError: Error | ErrorEvent,
type?: ManagedSocketErrorType, type?: ManagedSocketErrorType,
) { ) {
super(message); super(message);
this.name = "ManagedSocketError"; this.name = "ManagedSocketError";
this.originalError = originalError; this.originalError = originalError;
this.type = type ?? this.type; this.type = type ?? this.type;
} }
} }

View file

@ -1,12 +1,12 @@
import { ManagedSocketErrorType, SocketDisconnectCode } from "./constants";
import { ManagedSocketError } from "./errors"; import { ManagedSocketError } from "./errors";
import { SocketSpeak } from "./messages";
import { SocketDisconnectCode, ManagedSocketErrorType } from "./constants";
import { ManagedSocket } from "./managed-socket"; import { ManagedSocket } from "./managed-socket";
import { SocketSpeak } from "./messages";
export { export {
SocketSpeak, SocketSpeak,
ManagedSocket, ManagedSocket,
ManagedSocketError, ManagedSocketError,
SocketDisconnectCode, SocketDisconnectCode,
ManagedSocketErrorType, ManagedSocketErrorType,
}; };

View file

@ -13,231 +13,231 @@ type SocketCloseHandler = () => void;
type SocketCatostrophicErrorHandler = (error: ManagedSocketError) => void; type SocketCatostrophicErrorHandler = (error: ManagedSocketError) => void;
export type ManagedSocketConnectionState = export type ManagedSocketConnectionState =
| "connecting" | "connecting"
| "open" | "open"
| "closing" | "closing"
| "closed" | "closed"
| "reconnecting" | "reconnecting"
| "terminated"; | "terminated";
export class ManagedSocket { export class ManagedSocket {
public readonly url: string; public readonly url: string;
public state: ManagedSocketConnectionState = "connecting"; public state: ManagedSocketConnectionState = "connecting";
private ws: WebSocket; private ws: WebSocket;
private onMessageHandler: SocketDataHandler | null = null; private onMessageHandler: SocketDataHandler | null = null;
private onErrorHandler: SocketErrorHandler | null = null; private onErrorHandler: SocketErrorHandler | null = null;
private onOpenHandler: SocketOpenHandler | null = null; private onOpenHandler: SocketOpenHandler | null = null;
private onCloseHandler: SocketCloseHandler | null = null; private onCloseHandler: SocketCloseHandler | null = null;
private onCatostrophicErrorHandler: SocketCatostrophicErrorHandler | null = private onCatostrophicErrorHandler: SocketCatostrophicErrorHandler | null =
null; null;
private reconnectAttempts: number; private reconnectAttempts: number;
private maxReconnectDelay: number; private maxReconnectDelay: number;
private heartbeatInterval: Timer | null; private heartbeatInterval: Timer | null;
private messageQueue: unknown[] = []; private messageQueue: unknown[] = [];
private attemptReconnect = true; private attemptReconnect = true;
private debugMode = false; private debugMode = false;
constructor(url: string) { constructor(url: string) {
this.url = url; this.url = url;
this.ws = this.connect(); this.ws = this.connect();
this.reconnectAttempts = 0; this.reconnectAttempts = 0;
this.maxReconnectDelay = MAX_RECONNECT_DELAY; this.maxReconnectDelay = MAX_RECONNECT_DELAY;
this.heartbeatInterval = null; this.heartbeatInterval = null;
} }
get connected() { get connected() {
return this.ws.readyState === WebSocket.OPEN; return this.ws.readyState === WebSocket.OPEN;
} }
get native() { get native() {
return this.ws; return this.ws;
} }
set debug(value: boolean) { set debug(value: boolean) {
this.debugMode = value; this.debugMode = value;
} }
debugMessage(...args: unknown[]) { debugMessage(...args: unknown[]) {
if (this.debugMode) { if (this.debugMode) {
const output = ["[ManagedSocketDebug]", ...args]; const output = ["[ManagedSocketDebug]", ...args];
console.log(...output); console.log(...output);
} }
} }
private sendQueuedMessages() { private sendQueuedMessages() {
// Make a copy of the queue and clear it first. // Make a copy of the queue and clear it first.
// this way if the messages fail to send again, // this way if the messages fail to send again,
// they just get requeued // they just get requeued
const messagesToSend = [...this.messageQueue]; const messagesToSend = [...this.messageQueue];
this.messageQueue = []; this.messageQueue = [];
for (const message of messagesToSend) { for (const message of messagesToSend) {
this.send(message); this.send(message);
} }
} }
private connect() { private connect() {
this.ws = new WebSocket(this.url); this.ws = new WebSocket(this.url);
this.ws.addEventListener("open", () => { this.ws.addEventListener("open", () => {
this.reconnectAttempts = 0; this.reconnectAttempts = 0;
// Announce successful reconnects // Announce successful reconnects
if (this.state === "reconnecting") { if (this.state === "reconnecting") {
this.debugMessage("Reconnected to socket server"); this.debugMessage("Reconnected to socket server");
} }
this.onOpenHandler?.(); this.onOpenHandler?.();
this.state = "open"; this.state = "open";
this.startHeartbeat(); this.startHeartbeat();
this.sendQueuedMessages(); this.sendQueuedMessages();
}); });
this.ws.addEventListener("close", (event) => { this.ws.addEventListener("close", (event) => {
const { code } = event; const { code } = event;
this.debugMessage(code); this.debugMessage(code);
this.onCloseHandler?.(); this.onCloseHandler?.();
// If the code received was catostrophic, terminate the connection // If the code received was catostrophic, terminate the connection
if (SocketSpeak.isCatatrophicCloseCode(code)) { if (SocketSpeak.isCatatrophicCloseCode(code)) {
this.handleCatostophicError(code); this.handleCatostophicError(code);
} }
this.state = "closed"; this.state = "closed";
if (this.attemptReconnect) { if (this.attemptReconnect) {
this.debugMessage( this.debugMessage(
"Socket connection closed. Attempting reconnection...", "Socket connection closed. Attempting reconnection...",
); );
this.stopHeartbeat(); this.stopHeartbeat();
this.reconnect(); this.reconnect();
} }
}); });
this.ws.addEventListener("error", (error) => { this.ws.addEventListener("error", (error) => {
this.debugMessage("WebSocket error", error); this.debugMessage("WebSocket error", error);
const errorType = const errorType =
this.state === "connecting" this.state === "connecting"
? ManagedSocketErrorType.CONNECTION_REJECTED ? ManagedSocketErrorType.CONNECTION_REJECTED
: ManagedSocketErrorType.SOCKET_ERROR; : ManagedSocketErrorType.SOCKET_ERROR;
const socketError = new ManagedSocketError( const socketError = new ManagedSocketError(
errorType, errorType,
new Error(error.currentTarget?.toString()), new Error(error.currentTarget?.toString()),
errorType, errorType,
); );
if (this.onErrorHandler !== null) { if (this.onErrorHandler !== null) {
return this.onErrorHandler(socketError); return this.onErrorHandler(socketError);
} }
throw error; throw error;
}); });
this.ws.addEventListener("message", (event) => { this.ws.addEventListener("message", (event) => {
const message = SocketSpeak.deserialize(event.data.toString()); const message = SocketSpeak.deserialize(event.data.toString());
// Ignore heartbeats // Ignore heartbeats
if (message.type === "heartbeat") { if (message.type === "heartbeat") {
this.debugMessage("Received heartbeat"); this.debugMessage("Received heartbeat");
return; return;
} }
if (message.data === undefined || message.data === null) { if (message.data === undefined || message.data === null) {
this.debugMessage("Received message with no data"); this.debugMessage("Received message with no data");
return; return;
} }
this.debugMessage("Received message", message.data); this.debugMessage("Received message", message.data);
this.onMessageHandler?.(message.data); this.onMessageHandler?.(message.data);
}); });
return this.ws; return this.ws;
} }
handleCatostophicError(code: SocketDisconnectCode) { handleCatostophicError(code: SocketDisconnectCode) {
this.state = "terminated"; this.state = "terminated";
this.attemptReconnect = false; this.attemptReconnect = false;
this.debugMessage( this.debugMessage(
"Socket connection terminated due to non-reconnect close code", "Socket connection terminated due to non-reconnect close code",
); );
const socketError = new ManagedSocketError( const socketError = new ManagedSocketError(
"Socket connection terminated due to non-reconnect close code", "Socket connection terminated due to non-reconnect close code",
new Error(code.toString()), new Error(code.toString()),
ManagedSocketErrorType.CATOSTROPHIC_CLOSE, ManagedSocketErrorType.CATOSTROPHIC_CLOSE,
); );
if (this.onCatostrophicErrorHandler !== null) { if (this.onCatostrophicErrorHandler !== null) {
return this.onCatostrophicErrorHandler(socketError); return this.onCatostrophicErrorHandler(socketError);
} }
throw socketError; throw socketError;
} }
onMessage(handler: SocketDataHandler) { onMessage(handler: SocketDataHandler) {
this.onMessageHandler = handler; this.onMessageHandler = handler;
} }
onError(handler: (error: ManagedSocketError) => void) { onError(handler: (error: ManagedSocketError) => void) {
this.onErrorHandler = handler; this.onErrorHandler = handler;
} }
onOpen(handler: () => void) { onOpen(handler: () => void) {
this.onOpenHandler = handler; this.onOpenHandler = handler;
} }
onClose(handler: () => void) { onClose(handler: () => void) {
this.onCloseHandler = handler; this.onCloseHandler = handler;
} }
reconnect() { reconnect() {
this.attemptReconnect = true; this.attemptReconnect = true;
this.state = "reconnecting"; this.state = "reconnecting";
const delay = Math.min( const delay = Math.min(
1000 * 2 ** this.reconnectAttempts, 1000 * 2 ** this.reconnectAttempts,
this.maxReconnectDelay, this.maxReconnectDelay,
); );
this.debugMessage(`Attempting to reconnect in ${delay}ms`); this.debugMessage(`Attempting to reconnect in ${delay}ms`);
setTimeout(() => { setTimeout(() => {
this.reconnectAttempts++; this.reconnectAttempts++;
this.connect(); this.connect();
}, delay); }, delay);
} }
close() { close() {
this.attemptReconnect = false; this.attemptReconnect = false;
this.state = "closing"; this.state = "closing";
this.ws.close(); this.ws.close();
} }
private startHeartbeat() { private startHeartbeat() {
this.heartbeatInterval = setInterval(() => { this.heartbeatInterval = setInterval(() => {
if (this.ws.readyState === WebSocket.OPEN) { if (this.ws.readyState === WebSocket.OPEN) {
this.ws.send( this.ws.send(
SocketSpeak.serialize(SocketSpeak.createHeartbeatMessage()), SocketSpeak.serialize(SocketSpeak.createHeartbeatMessage()),
); );
} }
}, HEARTBEAT_INTERVAL); }, HEARTBEAT_INTERVAL);
} }
private stopHeartbeat() { private stopHeartbeat() {
if (this.heartbeatInterval !== null) { if (this.heartbeatInterval !== null) {
clearInterval(this.heartbeatInterval); clearInterval(this.heartbeatInterval);
this.heartbeatInterval = null; this.heartbeatInterval = null;
} }
} }
send<T>(data: T) { send<T>(data: T) {
if (this.ws.readyState !== WebSocket.OPEN) { if (this.ws.readyState !== WebSocket.OPEN) {
this.debugMessage("WebSocket is not open. Queuing message."); this.debugMessage("WebSocket is not open. Queuing message.");
this.messageQueue.push(data); this.messageQueue.push(data);
return; return;
} }
this.ws.send(SocketSpeak.prepare(data)); this.ws.send(SocketSpeak.prepare(data));
} }
} }

View file

@ -1,11 +1,11 @@
import { ManagedSocketErrorType, NON_RECONNECT_CODES } from "./constants"; import { ManagedSocketErrorType, NON_RECONNECT_CODES } from "./constants";
import { ManagedSocketError } from "./errors"; import { ManagedSocketError } from "./errors";
import { import {
parseAnyScoketMessage, type SerializedSocketMessage,
type SerializedSocketMessage, type SocketDataMessage,
type SocketDataMessage, type SocketHeartbeatMessage,
type SocketHeartbeatMessage, type SocketMessage,
type SocketMessage, parseAnyScoketMessage,
} from "./types"; } from "./types";
/** /**
@ -15,10 +15,10 @@ import {
* @returns An object representing the socket data message with a type of "message" and the provided data. * @returns An object representing the socket data message with a type of "message" and the provided data.
*/ */
function createDataMessage(data: unknown): SocketDataMessage { function createDataMessage(data: unknown): SocketDataMessage {
return { return {
type: "message", type: "message",
data, data,
}; };
} }
/** /**
@ -27,10 +27,10 @@ function createDataMessage(data: unknown): SocketDataMessage {
* @returns {SocketHeartbeatMessage} An object representing a heartbeat message with the current timestamp. * @returns {SocketHeartbeatMessage} An object representing a heartbeat message with the current timestamp.
*/ */
function createHeartbeatMessage(): SocketHeartbeatMessage { function createHeartbeatMessage(): SocketHeartbeatMessage {
return { return {
type: "heartbeat", type: "heartbeat",
data: new Date().toISOString(), data: new Date().toISOString(),
}; };
} }
/** /**
@ -40,7 +40,7 @@ function createHeartbeatMessage(): SocketHeartbeatMessage {
* @returns {SerializedSocketMessage} The serialized JSON string representation of the message. * @returns {SerializedSocketMessage} The serialized JSON string representation of the message.
*/ */
function serialize(message: SocketMessage): SerializedSocketMessage { function serialize(message: SocketMessage): SerializedSocketMessage {
return JSON.stringify(message) as SerializedSocketMessage; return JSON.stringify(message) as SerializedSocketMessage;
} }
/** /**
@ -51,16 +51,16 @@ function serialize(message: SocketMessage): SerializedSocketMessage {
* @throws {ManagedSocketError} If the JSON string cannot be parsed or if the message shape is invalid. * @throws {ManagedSocketError} If the JSON string cannot be parsed or if the message shape is invalid.
*/ */
function deserialize(data: string): SocketMessage { function deserialize(data: string): SocketMessage {
try { try {
const asObj = JSON.parse(data); const asObj = JSON.parse(data);
return parseAnyScoketMessage(asObj); return parseAnyScoketMessage(asObj);
} catch (error) { } catch (error) {
throw new ManagedSocketError( throw new ManagedSocketError(
"Failed to parse incoming message shape", "Failed to parse incoming message shape",
error instanceof Error ? error : new Error("Unknown error"), error instanceof Error ? error : new Error("Unknown error"),
ManagedSocketErrorType.INVALID_MESSAGE_SHAPE, ManagedSocketErrorType.INVALID_MESSAGE_SHAPE,
); );
} }
} }
/** /**
@ -75,7 +75,7 @@ function deserialize(data: string): SocketMessage {
* @returns {SerializedSocketMessage} - The serialized socket message. * @returns {SerializedSocketMessage} - The serialized socket message.
*/ */
function prepare<T>(data: T): SerializedSocketMessage { function prepare<T>(data: T): SerializedSocketMessage {
return serialize(createDataMessage(data)); return serialize(createDataMessage(data));
} }
/** /**
@ -85,14 +85,14 @@ function prepare<T>(data: T): SerializedSocketMessage {
* @returns `true` if the code is in the list of non-reconnect codes, otherwise `false`. * @returns `true` if the code is in the list of non-reconnect codes, otherwise `false`.
*/ */
function isCatatrophicCloseCode(code: number) { function isCatatrophicCloseCode(code: number) {
return (NON_RECONNECT_CODES as readonly number[]).includes(code); return (NON_RECONNECT_CODES as readonly number[]).includes(code);
} }
export const SocketSpeak = { export const SocketSpeak = {
createDataMessage, createDataMessage,
createHeartbeatMessage, createHeartbeatMessage,
serialize, serialize,
deserialize, deserialize,
isCatatrophicCloseCode, isCatatrophicCloseCode,
prepare, prepare,
}; };

View file

@ -8,7 +8,7 @@ declare const serializedSocketMessageBrand: unique symbol;
* This type is a branded string, ensuring type safety for serialized socket messages. * This type is a branded string, ensuring type safety for serialized socket messages.
*/ */
export type SerializedSocketMessage = string & { export type SerializedSocketMessage = string & {
[serializedSocketMessageBrand]: true; [serializedSocketMessageBrand]: true;
}; };
/** /**
@ -16,16 +16,16 @@ export type SerializedSocketMessage = string & {
* This message is used to indicate that the connection is still alive. * This message is used to indicate that the connection is still alive.
*/ */
export interface SocketHeartbeatMessage { export interface SocketHeartbeatMessage {
type: "heartbeat"; type: "heartbeat";
data: string; data: string;
} }
/** /**
* Represents a message with data to be handled by the application * Represents a message with data to be handled by the application
*/ */
export interface SocketDataMessage { export interface SocketDataMessage {
type: "message"; type: "message";
data: unknown; data: unknown;
} }
/** /**
@ -44,18 +44,18 @@ export type SocketMessageType = (typeof MESSAGE_TYPES)[number];
* @throws {ManagedSocketError} If the value is not a string or not included in `MESSAGE_TYPES`. * @throws {ManagedSocketError} If the value is not a string or not included in `MESSAGE_TYPES`.
*/ */
export function parseMessageType(value: unknown): SocketMessageType { export function parseMessageType(value: unknown): SocketMessageType {
if ( if (
typeof value !== "string" || typeof value !== "string" ||
!(MESSAGE_TYPES as readonly string[]).includes(value) !(MESSAGE_TYPES as readonly string[]).includes(value)
) { ) {
throw new ManagedSocketError( throw new ManagedSocketError(
"Failed to parse incoming message shape", "Failed to parse incoming message shape",
new Error("Invalid message type"), new Error("Invalid message type"),
ManagedSocketErrorType.INVALID_MESSAGE_SHAPE, ManagedSocketErrorType.INVALID_MESSAGE_SHAPE,
); );
} }
return value as SocketMessageType; return value as SocketMessageType;
} }
/** /**
@ -66,50 +66,50 @@ export function parseMessageType(value: unknown): SocketMessageType {
* @throws {ManagedSocketError} If the incoming message is not a parseable message * @throws {ManagedSocketError} If the incoming message is not a parseable message
*/ */
export function parseAnyScoketMessage(value: unknown): SocketMessage { export function parseAnyScoketMessage(value: unknown): SocketMessage {
if (typeof value !== "object" || value === null) { if (typeof value !== "object" || value === null) {
throw new ManagedSocketError( throw new ManagedSocketError(
"Failed to parse incoming message shape: value was not an object", "Failed to parse incoming message shape: value was not an object",
new Error("Invalid message shape"), new Error("Invalid message shape"),
ManagedSocketErrorType.INVALID_MESSAGE_SHAPE, ManagedSocketErrorType.INVALID_MESSAGE_SHAPE,
); );
} }
if (!("type" in value) || !("data" in value)) { if (!("type" in value) || !("data" in value)) {
throw new ManagedSocketError( throw new ManagedSocketError(
"Failed to parse incoming message shape: missing type or data", "Failed to parse incoming message shape: missing type or data",
new Error("Invalid message shape"), new Error("Invalid message shape"),
ManagedSocketErrorType.INVALID_MESSAGE_SHAPE, ManagedSocketErrorType.INVALID_MESSAGE_SHAPE,
); );
} }
const { type: incomingType, data: incomingData } = value; const { type: incomingType, data: incomingData } = value;
const type = parseMessageType(incomingType); const type = parseMessageType(incomingType);
if (type === "heartbeat") { if (type === "heartbeat") {
if (typeof incomingData !== "string") { if (typeof incomingData !== "string") {
throw new ManagedSocketError( throw new ManagedSocketError(
"Failed to parse incoming message shape: heartbeat data was not a string", "Failed to parse incoming message shape: heartbeat data was not a string",
new Error("Invalid message shape"), new Error("Invalid message shape"),
ManagedSocketErrorType.INVALID_MESSAGE_SHAPE, ManagedSocketErrorType.INVALID_MESSAGE_SHAPE,
); );
} }
return { return {
type, type,
data: incomingData, data: incomingData,
}; };
} }
if (type === "message") { if (type === "message") {
return { return {
type, type,
data: incomingData, data: incomingData,
}; };
} }
throw new ManagedSocketError( throw new ManagedSocketError(
"Failed to parse incoming message shape", "Failed to parse incoming message shape",
new Error("Invalid message type"), new Error("Invalid message type"),
ManagedSocketErrorType.INVALID_MESSAGE_SHAPE, ManagedSocketErrorType.INVALID_MESSAGE_SHAPE,
); );
} }