Add biome and autofix
This commit is contained in:
parent
0036079c75
commit
ba73b0a1f7
10 changed files with 350 additions and 318 deletions
31
biome.json
Normal file
31
biome.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
8
build.ts
8
build.ts
|
@ -1,8 +1,8 @@
|
|||
import dts from "bun-plugin-dts";
|
||||
|
||||
await Bun.build({
|
||||
entrypoints: ["./src/index.ts"],
|
||||
outdir: "./dist",
|
||||
plugins: [dts()],
|
||||
target: "browser",
|
||||
entrypoints: ["./src/index.ts"],
|
||||
outdir: "./dist",
|
||||
plugins: [dts()],
|
||||
target: "browser",
|
||||
});
|
||||
|
|
BIN
bun.lockb
BIN
bun.lockb
Binary file not shown.
|
@ -15,6 +15,7 @@
|
|||
],
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^1.9.4",
|
||||
"@types/bun": "latest",
|
||||
"bun-plugin-dts": "^0.2.3"
|
||||
},
|
||||
|
|
|
@ -1,31 +1,31 @@
|
|||
export enum SocketDisconnectCode {
|
||||
CLOSE_NORMAL = 1000,
|
||||
CLOSE_GOING_AWAY = 1001,
|
||||
CLOSE_PROTOCOL_ERROR = 1002,
|
||||
CLOSE_UNSUPPORTED = 1003,
|
||||
CLOSE_NO_STATUS = 1005,
|
||||
CLOSE_ABNORMAL = 1006,
|
||||
CLOSE_TOO_LARGE = 1009,
|
||||
CLOSE_EXTENSION_REQUIRED = 1010,
|
||||
CLOSE_INTERNAL_ERROR = 1011,
|
||||
CLOSE_SERVICE_RESTART = 1012,
|
||||
CLOSE_TRY_AGAIN_LATER = 1013,
|
||||
CLOSE_TLS_HANDSHAKE = 1015,
|
||||
CLOSE_NORMAL = 1000,
|
||||
CLOSE_GOING_AWAY = 1001,
|
||||
CLOSE_PROTOCOL_ERROR = 1002,
|
||||
CLOSE_UNSUPPORTED = 1003,
|
||||
CLOSE_NO_STATUS = 1005,
|
||||
CLOSE_ABNORMAL = 1006,
|
||||
CLOSE_TOO_LARGE = 1009,
|
||||
CLOSE_EXTENSION_REQUIRED = 1010,
|
||||
CLOSE_INTERNAL_ERROR = 1011,
|
||||
CLOSE_SERVICE_RESTART = 1012,
|
||||
CLOSE_TRY_AGAIN_LATER = 1013,
|
||||
CLOSE_TLS_HANDSHAKE = 1015,
|
||||
}
|
||||
|
||||
export const NON_RECONNECT_CODES = [
|
||||
SocketDisconnectCode.CLOSE_NORMAL,
|
||||
SocketDisconnectCode.CLOSE_GOING_AWAY,
|
||||
SocketDisconnectCode.CLOSE_UNSUPPORTED,
|
||||
SocketDisconnectCode.CLOSE_TOO_LARGE,
|
||||
SocketDisconnectCode.CLOSE_EXTENSION_REQUIRED,
|
||||
SocketDisconnectCode.CLOSE_TRY_AGAIN_LATER,
|
||||
SocketDisconnectCode.CLOSE_TLS_HANDSHAKE,
|
||||
SocketDisconnectCode.CLOSE_NORMAL,
|
||||
SocketDisconnectCode.CLOSE_GOING_AWAY,
|
||||
SocketDisconnectCode.CLOSE_UNSUPPORTED,
|
||||
SocketDisconnectCode.CLOSE_TOO_LARGE,
|
||||
SocketDisconnectCode.CLOSE_EXTENSION_REQUIRED,
|
||||
SocketDisconnectCode.CLOSE_TRY_AGAIN_LATER,
|
||||
SocketDisconnectCode.CLOSE_TLS_HANDSHAKE,
|
||||
] as const;
|
||||
|
||||
export enum ManagedSocketErrorType {
|
||||
INVALID_MESSAGE_SHAPE = "Invalid message shape.",
|
||||
CATOSTROPHIC_CLOSE = "Catostrophic close code",
|
||||
SOCKET_ERROR = "WebSocket error",
|
||||
CONNECTION_REJECTED = "Connection rejected",
|
||||
INVALID_MESSAGE_SHAPE = "Invalid message shape.",
|
||||
CATOSTROPHIC_CLOSE = "Catostrophic close code",
|
||||
SOCKET_ERROR = "WebSocket error",
|
||||
CONNECTION_REJECTED = "Connection rejected",
|
||||
}
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
import { ManagedSocketErrorType } from "./constants";
|
||||
|
||||
export class ManagedSocketError extends Error {
|
||||
public originalError: Error | ErrorEvent;
|
||||
public type: ManagedSocketErrorType = ManagedSocketErrorType.SOCKET_ERROR;
|
||||
public originalError: Error | ErrorEvent;
|
||||
public type: ManagedSocketErrorType = ManagedSocketErrorType.SOCKET_ERROR;
|
||||
|
||||
constructor(
|
||||
message: string,
|
||||
originalError: Error | ErrorEvent,
|
||||
type?: ManagedSocketErrorType,
|
||||
) {
|
||||
super(message);
|
||||
this.name = "ManagedSocketError";
|
||||
this.originalError = originalError;
|
||||
this.type = type ?? this.type;
|
||||
}
|
||||
constructor(
|
||||
message: string,
|
||||
originalError: Error | ErrorEvent,
|
||||
type?: ManagedSocketErrorType,
|
||||
) {
|
||||
super(message);
|
||||
this.name = "ManagedSocketError";
|
||||
this.originalError = originalError;
|
||||
this.type = type ?? this.type;
|
||||
}
|
||||
}
|
||||
|
|
14
src/index.ts
14
src/index.ts
|
@ -1,12 +1,12 @@
|
|||
import { ManagedSocketErrorType, SocketDisconnectCode } from "./constants";
|
||||
import { ManagedSocketError } from "./errors";
|
||||
import { SocketSpeak } from "./messages";
|
||||
import { SocketDisconnectCode, ManagedSocketErrorType } from "./constants";
|
||||
import { ManagedSocket } from "./managed-socket";
|
||||
import { SocketSpeak } from "./messages";
|
||||
|
||||
export {
|
||||
SocketSpeak,
|
||||
ManagedSocket,
|
||||
ManagedSocketError,
|
||||
SocketDisconnectCode,
|
||||
ManagedSocketErrorType,
|
||||
SocketSpeak,
|
||||
ManagedSocket,
|
||||
ManagedSocketError,
|
||||
SocketDisconnectCode,
|
||||
ManagedSocketErrorType,
|
||||
};
|
||||
|
|
|
@ -13,231 +13,231 @@ type SocketCloseHandler = () => void;
|
|||
type SocketCatostrophicErrorHandler = (error: ManagedSocketError) => void;
|
||||
|
||||
export type ManagedSocketConnectionState =
|
||||
| "connecting"
|
||||
| "open"
|
||||
| "closing"
|
||||
| "closed"
|
||||
| "reconnecting"
|
||||
| "terminated";
|
||||
| "connecting"
|
||||
| "open"
|
||||
| "closing"
|
||||
| "closed"
|
||||
| "reconnecting"
|
||||
| "terminated";
|
||||
|
||||
export class ManagedSocket {
|
||||
public readonly url: string;
|
||||
public state: ManagedSocketConnectionState = "connecting";
|
||||
private ws: WebSocket;
|
||||
public readonly url: string;
|
||||
public state: ManagedSocketConnectionState = "connecting";
|
||||
private ws: WebSocket;
|
||||
|
||||
private onMessageHandler: SocketDataHandler | null = null;
|
||||
private onErrorHandler: SocketErrorHandler | null = null;
|
||||
private onOpenHandler: SocketOpenHandler | null = null;
|
||||
private onCloseHandler: SocketCloseHandler | null = null;
|
||||
private onCatostrophicErrorHandler: SocketCatostrophicErrorHandler | null =
|
||||
null;
|
||||
private onMessageHandler: SocketDataHandler | null = null;
|
||||
private onErrorHandler: SocketErrorHandler | null = null;
|
||||
private onOpenHandler: SocketOpenHandler | null = null;
|
||||
private onCloseHandler: SocketCloseHandler | null = null;
|
||||
private onCatostrophicErrorHandler: SocketCatostrophicErrorHandler | null =
|
||||
null;
|
||||
|
||||
private reconnectAttempts: number;
|
||||
private maxReconnectDelay: number;
|
||||
private heartbeatInterval: Timer | null;
|
||||
private messageQueue: unknown[] = [];
|
||||
private attemptReconnect = true;
|
||||
private reconnectAttempts: number;
|
||||
private maxReconnectDelay: number;
|
||||
private heartbeatInterval: Timer | null;
|
||||
private messageQueue: unknown[] = [];
|
||||
private attemptReconnect = true;
|
||||
|
||||
private debugMode = false;
|
||||
private debugMode = false;
|
||||
|
||||
constructor(url: string) {
|
||||
this.url = url;
|
||||
this.ws = this.connect();
|
||||
this.reconnectAttempts = 0;
|
||||
this.maxReconnectDelay = MAX_RECONNECT_DELAY;
|
||||
this.heartbeatInterval = null;
|
||||
}
|
||||
constructor(url: string) {
|
||||
this.url = url;
|
||||
this.ws = this.connect();
|
||||
this.reconnectAttempts = 0;
|
||||
this.maxReconnectDelay = MAX_RECONNECT_DELAY;
|
||||
this.heartbeatInterval = null;
|
||||
}
|
||||
|
||||
get connected() {
|
||||
return this.ws.readyState === WebSocket.OPEN;
|
||||
}
|
||||
get connected() {
|
||||
return this.ws.readyState === WebSocket.OPEN;
|
||||
}
|
||||
|
||||
get native() {
|
||||
return this.ws;
|
||||
}
|
||||
get native() {
|
||||
return this.ws;
|
||||
}
|
||||
|
||||
set debug(value: boolean) {
|
||||
this.debugMode = value;
|
||||
}
|
||||
set debug(value: boolean) {
|
||||
this.debugMode = value;
|
||||
}
|
||||
|
||||
debugMessage(...args: unknown[]) {
|
||||
if (this.debugMode) {
|
||||
const output = ["[ManagedSocketDebug]", ...args];
|
||||
console.log(...output);
|
||||
}
|
||||
}
|
||||
debugMessage(...args: unknown[]) {
|
||||
if (this.debugMode) {
|
||||
const output = ["[ManagedSocketDebug]", ...args];
|
||||
console.log(...output);
|
||||
}
|
||||
}
|
||||
|
||||
private sendQueuedMessages() {
|
||||
// Make a copy of the queue and clear it first.
|
||||
// this way if the messages fail to send again,
|
||||
// they just get requeued
|
||||
const messagesToSend = [...this.messageQueue];
|
||||
this.messageQueue = [];
|
||||
for (const message of messagesToSend) {
|
||||
this.send(message);
|
||||
}
|
||||
}
|
||||
private sendQueuedMessages() {
|
||||
// Make a copy of the queue and clear it first.
|
||||
// this way if the messages fail to send again,
|
||||
// they just get requeued
|
||||
const messagesToSend = [...this.messageQueue];
|
||||
this.messageQueue = [];
|
||||
for (const message of messagesToSend) {
|
||||
this.send(message);
|
||||
}
|
||||
}
|
||||
|
||||
private connect() {
|
||||
this.ws = new WebSocket(this.url);
|
||||
private connect() {
|
||||
this.ws = new WebSocket(this.url);
|
||||
|
||||
this.ws.addEventListener("open", () => {
|
||||
this.reconnectAttempts = 0;
|
||||
this.ws.addEventListener("open", () => {
|
||||
this.reconnectAttempts = 0;
|
||||
|
||||
// Announce successful reconnects
|
||||
if (this.state === "reconnecting") {
|
||||
this.debugMessage("Reconnected to socket server");
|
||||
}
|
||||
// Announce successful reconnects
|
||||
if (this.state === "reconnecting") {
|
||||
this.debugMessage("Reconnected to socket server");
|
||||
}
|
||||
|
||||
this.onOpenHandler?.();
|
||||
this.state = "open";
|
||||
this.startHeartbeat();
|
||||
this.sendQueuedMessages();
|
||||
});
|
||||
this.onOpenHandler?.();
|
||||
this.state = "open";
|
||||
this.startHeartbeat();
|
||||
this.sendQueuedMessages();
|
||||
});
|
||||
|
||||
this.ws.addEventListener("close", (event) => {
|
||||
const { code } = event;
|
||||
this.debugMessage(code);
|
||||
this.onCloseHandler?.();
|
||||
this.ws.addEventListener("close", (event) => {
|
||||
const { code } = event;
|
||||
this.debugMessage(code);
|
||||
this.onCloseHandler?.();
|
||||
|
||||
// If the code received was catostrophic, terminate the connection
|
||||
if (SocketSpeak.isCatatrophicCloseCode(code)) {
|
||||
this.handleCatostophicError(code);
|
||||
}
|
||||
// If the code received was catostrophic, terminate the connection
|
||||
if (SocketSpeak.isCatatrophicCloseCode(code)) {
|
||||
this.handleCatostophicError(code);
|
||||
}
|
||||
|
||||
this.state = "closed";
|
||||
this.state = "closed";
|
||||
|
||||
if (this.attemptReconnect) {
|
||||
this.debugMessage(
|
||||
"Socket connection closed. Attempting reconnection...",
|
||||
);
|
||||
this.stopHeartbeat();
|
||||
this.reconnect();
|
||||
}
|
||||
});
|
||||
if (this.attemptReconnect) {
|
||||
this.debugMessage(
|
||||
"Socket connection closed. Attempting reconnection...",
|
||||
);
|
||||
this.stopHeartbeat();
|
||||
this.reconnect();
|
||||
}
|
||||
});
|
||||
|
||||
this.ws.addEventListener("error", (error) => {
|
||||
this.debugMessage("WebSocket error", error);
|
||||
this.ws.addEventListener("error", (error) => {
|
||||
this.debugMessage("WebSocket error", error);
|
||||
|
||||
const errorType =
|
||||
this.state === "connecting"
|
||||
? ManagedSocketErrorType.CONNECTION_REJECTED
|
||||
: ManagedSocketErrorType.SOCKET_ERROR;
|
||||
const errorType =
|
||||
this.state === "connecting"
|
||||
? ManagedSocketErrorType.CONNECTION_REJECTED
|
||||
: ManagedSocketErrorType.SOCKET_ERROR;
|
||||
|
||||
const socketError = new ManagedSocketError(
|
||||
errorType,
|
||||
new Error(error.currentTarget?.toString()),
|
||||
errorType,
|
||||
);
|
||||
const socketError = new ManagedSocketError(
|
||||
errorType,
|
||||
new Error(error.currentTarget?.toString()),
|
||||
errorType,
|
||||
);
|
||||
|
||||
if (this.onErrorHandler !== null) {
|
||||
return this.onErrorHandler(socketError);
|
||||
}
|
||||
if (this.onErrorHandler !== null) {
|
||||
return this.onErrorHandler(socketError);
|
||||
}
|
||||
|
||||
throw error;
|
||||
});
|
||||
throw error;
|
||||
});
|
||||
|
||||
this.ws.addEventListener("message", (event) => {
|
||||
const message = SocketSpeak.deserialize(event.data.toString());
|
||||
this.ws.addEventListener("message", (event) => {
|
||||
const message = SocketSpeak.deserialize(event.data.toString());
|
||||
|
||||
// Ignore heartbeats
|
||||
if (message.type === "heartbeat") {
|
||||
this.debugMessage("Received heartbeat");
|
||||
return;
|
||||
}
|
||||
// Ignore heartbeats
|
||||
if (message.type === "heartbeat") {
|
||||
this.debugMessage("Received heartbeat");
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.data === undefined || message.data === null) {
|
||||
this.debugMessage("Received message with no data");
|
||||
return;
|
||||
}
|
||||
if (message.data === undefined || message.data === null) {
|
||||
this.debugMessage("Received message with no data");
|
||||
return;
|
||||
}
|
||||
|
||||
this.debugMessage("Received message", message.data);
|
||||
this.onMessageHandler?.(message.data);
|
||||
});
|
||||
this.debugMessage("Received message", message.data);
|
||||
this.onMessageHandler?.(message.data);
|
||||
});
|
||||
|
||||
return this.ws;
|
||||
}
|
||||
return this.ws;
|
||||
}
|
||||
|
||||
handleCatostophicError(code: SocketDisconnectCode) {
|
||||
this.state = "terminated";
|
||||
this.attemptReconnect = false;
|
||||
this.debugMessage(
|
||||
"Socket connection terminated due to non-reconnect close code",
|
||||
);
|
||||
handleCatostophicError(code: SocketDisconnectCode) {
|
||||
this.state = "terminated";
|
||||
this.attemptReconnect = false;
|
||||
this.debugMessage(
|
||||
"Socket connection terminated due to non-reconnect close code",
|
||||
);
|
||||
|
||||
const socketError = new ManagedSocketError(
|
||||
"Socket connection terminated due to non-reconnect close code",
|
||||
new Error(code.toString()),
|
||||
ManagedSocketErrorType.CATOSTROPHIC_CLOSE,
|
||||
);
|
||||
const socketError = new ManagedSocketError(
|
||||
"Socket connection terminated due to non-reconnect close code",
|
||||
new Error(code.toString()),
|
||||
ManagedSocketErrorType.CATOSTROPHIC_CLOSE,
|
||||
);
|
||||
|
||||
if (this.onCatostrophicErrorHandler !== null) {
|
||||
return this.onCatostrophicErrorHandler(socketError);
|
||||
}
|
||||
if (this.onCatostrophicErrorHandler !== null) {
|
||||
return this.onCatostrophicErrorHandler(socketError);
|
||||
}
|
||||
|
||||
throw socketError;
|
||||
}
|
||||
throw socketError;
|
||||
}
|
||||
|
||||
onMessage(handler: SocketDataHandler) {
|
||||
this.onMessageHandler = handler;
|
||||
}
|
||||
onMessage(handler: SocketDataHandler) {
|
||||
this.onMessageHandler = handler;
|
||||
}
|
||||
|
||||
onError(handler: (error: ManagedSocketError) => void) {
|
||||
this.onErrorHandler = handler;
|
||||
}
|
||||
onError(handler: (error: ManagedSocketError) => void) {
|
||||
this.onErrorHandler = handler;
|
||||
}
|
||||
|
||||
onOpen(handler: () => void) {
|
||||
this.onOpenHandler = handler;
|
||||
}
|
||||
onOpen(handler: () => void) {
|
||||
this.onOpenHandler = handler;
|
||||
}
|
||||
|
||||
onClose(handler: () => void) {
|
||||
this.onCloseHandler = handler;
|
||||
}
|
||||
onClose(handler: () => void) {
|
||||
this.onCloseHandler = handler;
|
||||
}
|
||||
|
||||
reconnect() {
|
||||
this.attemptReconnect = true;
|
||||
this.state = "reconnecting";
|
||||
const delay = Math.min(
|
||||
1000 * 2 ** this.reconnectAttempts,
|
||||
this.maxReconnectDelay,
|
||||
);
|
||||
this.debugMessage(`Attempting to reconnect in ${delay}ms`);
|
||||
setTimeout(() => {
|
||||
this.reconnectAttempts++;
|
||||
this.connect();
|
||||
}, delay);
|
||||
}
|
||||
reconnect() {
|
||||
this.attemptReconnect = true;
|
||||
this.state = "reconnecting";
|
||||
const delay = Math.min(
|
||||
1000 * 2 ** this.reconnectAttempts,
|
||||
this.maxReconnectDelay,
|
||||
);
|
||||
this.debugMessage(`Attempting to reconnect in ${delay}ms`);
|
||||
setTimeout(() => {
|
||||
this.reconnectAttempts++;
|
||||
this.connect();
|
||||
}, delay);
|
||||
}
|
||||
|
||||
close() {
|
||||
this.attemptReconnect = false;
|
||||
this.state = "closing";
|
||||
this.ws.close();
|
||||
}
|
||||
close() {
|
||||
this.attemptReconnect = false;
|
||||
this.state = "closing";
|
||||
this.ws.close();
|
||||
}
|
||||
|
||||
private startHeartbeat() {
|
||||
this.heartbeatInterval = setInterval(() => {
|
||||
if (this.ws.readyState === WebSocket.OPEN) {
|
||||
this.ws.send(
|
||||
SocketSpeak.serialize(SocketSpeak.createHeartbeatMessage()),
|
||||
);
|
||||
}
|
||||
}, HEARTBEAT_INTERVAL);
|
||||
}
|
||||
private startHeartbeat() {
|
||||
this.heartbeatInterval = setInterval(() => {
|
||||
if (this.ws.readyState === WebSocket.OPEN) {
|
||||
this.ws.send(
|
||||
SocketSpeak.serialize(SocketSpeak.createHeartbeatMessage()),
|
||||
);
|
||||
}
|
||||
}, HEARTBEAT_INTERVAL);
|
||||
}
|
||||
|
||||
private stopHeartbeat() {
|
||||
if (this.heartbeatInterval !== null) {
|
||||
clearInterval(this.heartbeatInterval);
|
||||
this.heartbeatInterval = null;
|
||||
}
|
||||
}
|
||||
private stopHeartbeat() {
|
||||
if (this.heartbeatInterval !== null) {
|
||||
clearInterval(this.heartbeatInterval);
|
||||
this.heartbeatInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
send<T>(data: T) {
|
||||
if (this.ws.readyState !== WebSocket.OPEN) {
|
||||
this.debugMessage("WebSocket is not open. Queuing message.");
|
||||
this.messageQueue.push(data);
|
||||
return;
|
||||
}
|
||||
send<T>(data: T) {
|
||||
if (this.ws.readyState !== WebSocket.OPEN) {
|
||||
this.debugMessage("WebSocket is not open. Queuing message.");
|
||||
this.messageQueue.push(data);
|
||||
return;
|
||||
}
|
||||
|
||||
this.ws.send(SocketSpeak.prepare(data));
|
||||
}
|
||||
this.ws.send(SocketSpeak.prepare(data));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { ManagedSocketErrorType, NON_RECONNECT_CODES } from "./constants";
|
||||
import { ManagedSocketError } from "./errors";
|
||||
import {
|
||||
parseAnyScoketMessage,
|
||||
type SerializedSocketMessage,
|
||||
type SocketDataMessage,
|
||||
type SocketHeartbeatMessage,
|
||||
type SocketMessage,
|
||||
type SerializedSocketMessage,
|
||||
type SocketDataMessage,
|
||||
type SocketHeartbeatMessage,
|
||||
type SocketMessage,
|
||||
parseAnyScoketMessage,
|
||||
} from "./types";
|
||||
|
||||
/**
|
||||
|
@ -15,10 +15,10 @@ import {
|
|||
* @returns An object representing the socket data message with a type of "message" and the provided data.
|
||||
*/
|
||||
function createDataMessage(data: unknown): SocketDataMessage {
|
||||
return {
|
||||
type: "message",
|
||||
data,
|
||||
};
|
||||
return {
|
||||
type: "message",
|
||||
data,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -27,10 +27,10 @@ function createDataMessage(data: unknown): SocketDataMessage {
|
|||
* @returns {SocketHeartbeatMessage} An object representing a heartbeat message with the current timestamp.
|
||||
*/
|
||||
function createHeartbeatMessage(): SocketHeartbeatMessage {
|
||||
return {
|
||||
type: "heartbeat",
|
||||
data: new Date().toISOString(),
|
||||
};
|
||||
return {
|
||||
type: "heartbeat",
|
||||
data: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -40,7 +40,7 @@ function createHeartbeatMessage(): SocketHeartbeatMessage {
|
|||
* @returns {SerializedSocketMessage} The serialized JSON string representation of the message.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
function deserialize(data: string): SocketMessage {
|
||||
try {
|
||||
const asObj = JSON.parse(data);
|
||||
return parseAnyScoketMessage(asObj);
|
||||
} catch (error) {
|
||||
throw new ManagedSocketError(
|
||||
"Failed to parse incoming message shape",
|
||||
error instanceof Error ? error : new Error("Unknown error"),
|
||||
ManagedSocketErrorType.INVALID_MESSAGE_SHAPE,
|
||||
);
|
||||
}
|
||||
try {
|
||||
const asObj = JSON.parse(data);
|
||||
return parseAnyScoketMessage(asObj);
|
||||
} catch (error) {
|
||||
throw new ManagedSocketError(
|
||||
"Failed to parse incoming message shape",
|
||||
error instanceof Error ? error : new Error("Unknown error"),
|
||||
ManagedSocketErrorType.INVALID_MESSAGE_SHAPE,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -75,7 +75,7 @@ function deserialize(data: string): SocketMessage {
|
|||
* @returns {SerializedSocketMessage} - The serialized socket message.
|
||||
*/
|
||||
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`.
|
||||
*/
|
||||
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 = {
|
||||
createDataMessage,
|
||||
createHeartbeatMessage,
|
||||
serialize,
|
||||
deserialize,
|
||||
isCatatrophicCloseCode,
|
||||
prepare,
|
||||
createDataMessage,
|
||||
createHeartbeatMessage,
|
||||
serialize,
|
||||
deserialize,
|
||||
isCatatrophicCloseCode,
|
||||
prepare,
|
||||
};
|
||||
|
|
112
src/types.ts
112
src/types.ts
|
@ -8,7 +8,7 @@ declare const serializedSocketMessageBrand: unique symbol;
|
|||
* This type is a branded string, ensuring type safety for serialized socket messages.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
export interface SocketHeartbeatMessage {
|
||||
type: "heartbeat";
|
||||
data: string;
|
||||
type: "heartbeat";
|
||||
data: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a message with data to be handled by the application
|
||||
*/
|
||||
export interface SocketDataMessage {
|
||||
type: "message";
|
||||
data: unknown;
|
||||
type: "message";
|
||||
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`.
|
||||
*/
|
||||
export function parseMessageType(value: unknown): SocketMessageType {
|
||||
if (
|
||||
typeof value !== "string" ||
|
||||
!(MESSAGE_TYPES as readonly string[]).includes(value)
|
||||
) {
|
||||
throw new ManagedSocketError(
|
||||
"Failed to parse incoming message shape",
|
||||
new Error("Invalid message type"),
|
||||
ManagedSocketErrorType.INVALID_MESSAGE_SHAPE,
|
||||
);
|
||||
}
|
||||
if (
|
||||
typeof value !== "string" ||
|
||||
!(MESSAGE_TYPES as readonly string[]).includes(value)
|
||||
) {
|
||||
throw new ManagedSocketError(
|
||||
"Failed to parse incoming message shape",
|
||||
new Error("Invalid message type"),
|
||||
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
|
||||
*/
|
||||
export function parseAnyScoketMessage(value: unknown): SocketMessage {
|
||||
if (typeof value !== "object" || value === null) {
|
||||
throw new ManagedSocketError(
|
||||
"Failed to parse incoming message shape: value was not an object",
|
||||
new Error("Invalid message shape"),
|
||||
ManagedSocketErrorType.INVALID_MESSAGE_SHAPE,
|
||||
);
|
||||
}
|
||||
if (typeof value !== "object" || value === null) {
|
||||
throw new ManagedSocketError(
|
||||
"Failed to parse incoming message shape: value was not an object",
|
||||
new Error("Invalid message shape"),
|
||||
ManagedSocketErrorType.INVALID_MESSAGE_SHAPE,
|
||||
);
|
||||
}
|
||||
|
||||
if (!("type" in value) || !("data" in value)) {
|
||||
throw new ManagedSocketError(
|
||||
"Failed to parse incoming message shape: missing type or data",
|
||||
new Error("Invalid message shape"),
|
||||
ManagedSocketErrorType.INVALID_MESSAGE_SHAPE,
|
||||
);
|
||||
}
|
||||
if (!("type" in value) || !("data" in value)) {
|
||||
throw new ManagedSocketError(
|
||||
"Failed to parse incoming message shape: missing type or data",
|
||||
new Error("Invalid message shape"),
|
||||
ManagedSocketErrorType.INVALID_MESSAGE_SHAPE,
|
||||
);
|
||||
}
|
||||
|
||||
const { type: incomingType, data: incomingData } = value;
|
||||
const type = parseMessageType(incomingType);
|
||||
const { type: incomingType, data: incomingData } = value;
|
||||
const type = parseMessageType(incomingType);
|
||||
|
||||
if (type === "heartbeat") {
|
||||
if (typeof incomingData !== "string") {
|
||||
throw new ManagedSocketError(
|
||||
"Failed to parse incoming message shape: heartbeat data was not a string",
|
||||
new Error("Invalid message shape"),
|
||||
ManagedSocketErrorType.INVALID_MESSAGE_SHAPE,
|
||||
);
|
||||
}
|
||||
if (type === "heartbeat") {
|
||||
if (typeof incomingData !== "string") {
|
||||
throw new ManagedSocketError(
|
||||
"Failed to parse incoming message shape: heartbeat data was not a string",
|
||||
new Error("Invalid message shape"),
|
||||
ManagedSocketErrorType.INVALID_MESSAGE_SHAPE,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
type,
|
||||
data: incomingData,
|
||||
};
|
||||
}
|
||||
return {
|
||||
type,
|
||||
data: incomingData,
|
||||
};
|
||||
}
|
||||
|
||||
if (type === "message") {
|
||||
return {
|
||||
type,
|
||||
data: incomingData,
|
||||
};
|
||||
}
|
||||
if (type === "message") {
|
||||
return {
|
||||
type,
|
||||
data: incomingData,
|
||||
};
|
||||
}
|
||||
|
||||
throw new ManagedSocketError(
|
||||
"Failed to parse incoming message shape",
|
||||
new Error("Invalid message type"),
|
||||
ManagedSocketErrorType.INVALID_MESSAGE_SHAPE,
|
||||
);
|
||||
throw new ManagedSocketError(
|
||||
"Failed to parse incoming message shape",
|
||||
new Error("Invalid message type"),
|
||||
ManagedSocketErrorType.INVALID_MESSAGE_SHAPE,
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue