feat: enhance BenncClient with websocket-ts integration and improved reconnection logic
This commit is contained in:
192
README.md
192
README.md
@@ -2,54 +2,198 @@
|
|||||||
|
|
||||||
[](https://drone.jacknet.io/TerribleCodeClub/bennc-js)
|
[](https://drone.jacknet.io/TerribleCodeClub/bennc-js)
|
||||||
|
|
||||||
An implementation of the [BENNC](https://wiki.jacknet.io/books/simontech/chapter/bennc) client specification.
|
A TypeScript implementation of the [BENNC](https://wiki.jacknet.io/books/simontech/chapter/bennc) (Butlersaurus Ephemeral No NONCEnse Chat) protocol specification. This library provides both low-level protocol utilities and a high-level client for connecting to BENNC chat servers via WebSocket.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Complete BENNC Protocol Support**: All message types (0x0000-0xFFFF) including subscribe, basic chat, user data, keepalive, history, and unsubscribe
|
||||||
|
- **High-Level Client**: `BenncClient` class with automatic reconnection and event-driven architecture
|
||||||
|
- **Browser & Node.js Compatible**: Works in modern browsers and Node.js environments
|
||||||
|
- **TypeScript First**: Full type safety with comprehensive TypeScript definitions
|
||||||
|
- **Encryption Support**: Built-in Romulus-M AEAD encryption for secure messaging
|
||||||
|
- **Automatic Reconnection**: Configurable backoff strategies (constant, exponential)
|
||||||
|
- **Small Bundle Size**: Minimal dependencies and efficient implementation
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install @3t/bennc
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### Using the BenncClient (Recommended)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { BenncClient, MessageTypes } from '@3t/bennc';
|
||||||
|
|
||||||
|
const client = new BenncClient({
|
||||||
|
url: 'wss://your-bennc-server.com',
|
||||||
|
autoReconnect: true,
|
||||||
|
reconnectBackoff: 'exponential',
|
||||||
|
reconnectDelay: 1000,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Listen for events
|
||||||
|
client.addEventListener('connected', () => {
|
||||||
|
console.log('Connected to BENNC server');
|
||||||
|
|
||||||
|
// Subscribe to basic messages
|
||||||
|
client.subscribe(MessageTypes.Basic);
|
||||||
|
});
|
||||||
|
|
||||||
|
client.addEventListener('message:1', (event) => {
|
||||||
|
const { senderId, data } = event.detail;
|
||||||
|
console.log(`Message from ${senderId}:`, new TextDecoder().decode(data));
|
||||||
|
});
|
||||||
|
|
||||||
|
client.addEventListener('disconnected', (event) => {
|
||||||
|
console.log('Disconnected:', event.detail);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Connect to server
|
||||||
|
await client.connect();
|
||||||
|
|
||||||
|
// Send a message
|
||||||
|
client.sendBasicMessage('Hello, BENNC!');
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using Low-Level Protocol Functions
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { packers, unpackers, MessageTypes } from '@3t/bennc';
|
||||||
|
|
||||||
|
// Pack a basic message
|
||||||
|
const messageData = new TextEncoder().encode('Hello World');
|
||||||
|
const packet = packers[MessageTypes.Basic](messageData);
|
||||||
|
|
||||||
|
// Unpack incoming message
|
||||||
|
const incomingMessage = unpackers[MessageTypes.Basic](receivedData);
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Reference
|
||||||
|
|
||||||
|
### BenncClient
|
||||||
|
|
||||||
|
#### Constructor Options
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface BenncClientOptions {
|
||||||
|
url: string; // WebSocket server URL
|
||||||
|
protocols?: string[]; // WebSocket protocols
|
||||||
|
autoReconnect?: boolean; // Enable auto-reconnection (default: true)
|
||||||
|
reconnectBackoff?: 'constant' | 'exponential'; // Backoff strategy (default: 'exponential')
|
||||||
|
reconnectDelay?: number; // Reconnection delay in ms (default: 1000)
|
||||||
|
maxReconnectAttempts?: number; // Max reconnection attempts (default: 10)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Methods
|
||||||
|
|
||||||
|
- `connect(): Promise<void>` - Connect to the BENNC server
|
||||||
|
- `disconnect(): void` - Disconnect from the server
|
||||||
|
- `isConnected(): boolean` - Check connection status
|
||||||
|
- `subscribe(messageType: MessageTypes): void` - Subscribe to message type
|
||||||
|
- `unsubscribe(messageType: MessageTypes): void` - Unsubscribe from message type
|
||||||
|
- `sendBasicMessage(message: string, key?: Uint8Array): void` - Send encrypted chat message
|
||||||
|
- `sendUserDataRequest(username: string, colour: Color, clientId: string, key?: Uint8Array): void` - Request user data
|
||||||
|
- `sendUserDataResponse(username: string, colour: Color, clientId: string, key?: Uint8Array): void` - Respond with user data
|
||||||
|
- `sendKeepalive(): void` - Send keepalive message
|
||||||
|
- `requestHistory(): void` - Request message history
|
||||||
|
|
||||||
|
#### Events
|
||||||
|
|
||||||
|
The client extends `EventTarget` and emits the following events:
|
||||||
|
|
||||||
|
- `connected` - Successfully connected to server
|
||||||
|
- `disconnected` - Disconnected from server (detail: `{code, reason}`)
|
||||||
|
- `reconnecting` - Attempting to reconnect
|
||||||
|
- `error` - Connection or protocol error (detail: error object)
|
||||||
|
- `packet` - Raw incoming packet (detail: packet object)
|
||||||
|
- `message` - Parsed message (detail: `{messageType, senderId, data}`)
|
||||||
|
- `message:${messageType}` - Specific message type events (detail: `{senderId, data}`)
|
||||||
|
- `unknown-message` - Unknown message type received
|
||||||
|
- `parse-error` - Error parsing incoming message
|
||||||
|
|
||||||
|
### Message Types
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
enum MessageTypes {
|
||||||
|
Subscribe = 0x0000,
|
||||||
|
Basic = 0x0001,
|
||||||
|
UserDataRequest = 0x0002,
|
||||||
|
UserDataResponse = 0x0003,
|
||||||
|
Keepalive = 0x0005,
|
||||||
|
GetHistory = 0xfffe,
|
||||||
|
Unsubscribe = 0xffff,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Protocol Constants
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const MAX_DATA_LENGTH = 1000; // Maximum data payload size
|
||||||
|
const DEFAULT_KEY: Uint8Array; // Default encryption key
|
||||||
|
```
|
||||||
|
|
||||||
## Build
|
## Build
|
||||||
|
|
||||||
To build the BENNC-JS library, first clone this repository.
|
To build the BENNC-JS library from source:
|
||||||
|
|
||||||
Run the following commands from the root of the repository:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
$ git clone <repository-url>
|
||||||
|
$ cd bennc-js
|
||||||
$ npm install
|
$ npm install
|
||||||
$ npm run build
|
$ npm run build
|
||||||
```
|
```
|
||||||
|
|
||||||
The build output will be saved to the `dist` directory.
|
The build output will be saved to the `dist` directory.
|
||||||
|
|
||||||
## Development instructions
|
## Protocol Details
|
||||||
|
|
||||||
Requirements:
|
BENNC uses a binary protocol with the following message structure:
|
||||||
|
|
||||||
- The latest LTS builds of Node and npm.
|
**Client → Server**: `[Type(2)|Length(2)|Data(0-1000)]`
|
||||||
|
**Server → Client**: `[Type(2)|SenderId(4)|Length(2)|Data(0-1000)]`
|
||||||
|
|
||||||
Follow the instructions below to lint, test and build BENNC-JS.
|
- All integers are big-endian
|
||||||
|
- Maximum data payload is 1000 bytes (including encryption overhead)
|
||||||
|
- Encrypted message types (0x0001, 0x0002, 0x0003) use Romulus-M AEAD with 16-byte nonces
|
||||||
|
- Reserved sender IDs: 0xFFFFFF00-0xFFFFFFFF
|
||||||
|
|
||||||
#### Lint
|
## Development
|
||||||
|
|
||||||
|
Requirements: Node.js LTS and npm
|
||||||
|
|
||||||
|
### Commands
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ npm install
|
# Install dependencies
|
||||||
$ npm run lint
|
npm install
|
||||||
```
|
|
||||||
|
|
||||||
#### Test
|
# Run tests
|
||||||
|
npm run test
|
||||||
|
|
||||||
```bash
|
# Lint code
|
||||||
$ npm install
|
npm run lint
|
||||||
$ npm run test
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Build
|
# Format code
|
||||||
|
npm run format
|
||||||
|
|
||||||
```bash
|
# Build library
|
||||||
$ npm install
|
npm run build
|
||||||
$ npm run build
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Visual Studio Code
|
### Visual Studio Code
|
||||||
|
|
||||||
This repository contains the necessary configuration files to debug, test and build BENNC-JS using only Visual Studio Code.
|
This repository includes VS Code configuration for debugging and testing. Use `Ctrl+Shift+B` (or `⇧⌘B`) to run the build task.
|
||||||
|
|
||||||
Run the build task (`Ctrl+Shift+B` or `⇧⌘B`) to automatically compile the Typescript source files in the background.
|
Unit tests use [Jest](https://jestjs.io/) with VS Code support via the [Jest extension](https://marketplace.visualstudio.com/items?itemName=Orta.vscode-jest).
|
||||||
|
|
||||||
Unit tests use the [Jest](https://jestjs.io/) library. Support for Visual Studio Code is offered through the [Jest marketplace package](https://marketplace.visualstudio.com/items?itemName=Orta.vscode-jest) maintained by Orta.
|
## License
|
||||||
|
|
||||||
|
ISC License - see package.json for details.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
This is part of the BENNC protocol ecosystem. Please refer to the [BENNC specification](https://wiki.jacknet.io/books/simontech/chapter/bennc) for protocol details.
|
||||||
|
|||||||
14
package-lock.json
generated
14
package-lock.json
generated
@@ -12,7 +12,8 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@3t/romulus": "^1.0.2",
|
"@3t/romulus": "^1.0.2",
|
||||||
"@types/color": "^3.0.3",
|
"@types/color": "^3.0.3",
|
||||||
"color": "^4.2.0"
|
"color": "^4.2.0",
|
||||||
|
"websocket-ts": "^2.2.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^30.0.0",
|
"@types/jest": "^30.0.0",
|
||||||
@@ -4451,6 +4452,12 @@
|
|||||||
"makeerror": "1.0.12"
|
"makeerror": "1.0.12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/websocket-ts": {
|
||||||
|
"version": "2.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/websocket-ts/-/websocket-ts-2.2.1.tgz",
|
||||||
|
"integrity": "sha512-YKPDfxlK5qOheLZ2bTIiktZO1bpfGdNCPJmTEaPW7G9UXI1GKjDdeacOrsULUS000OPNxDVOyAuKLuIWPqWM0Q==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/which": {
|
"node_modules/which": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||||
@@ -7714,6 +7721,11 @@
|
|||||||
"makeerror": "1.0.12"
|
"makeerror": "1.0.12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"websocket-ts": {
|
||||||
|
"version": "2.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/websocket-ts/-/websocket-ts-2.2.1.tgz",
|
||||||
|
"integrity": "sha512-YKPDfxlK5qOheLZ2bTIiktZO1bpfGdNCPJmTEaPW7G9UXI1GKjDdeacOrsULUS000OPNxDVOyAuKLuIWPqWM0Q=="
|
||||||
|
},
|
||||||
"which": {
|
"which": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||||
|
|||||||
@@ -29,7 +29,8 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@3t/romulus": "^1.0.2",
|
"@3t/romulus": "^1.0.2",
|
||||||
"@types/color": "^3.0.3",
|
"@types/color": "^3.0.3",
|
||||||
"color": "^4.2.0"
|
"color": "^4.2.0",
|
||||||
|
"websocket-ts": "^2.2.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^30.0.0",
|
"@types/jest": "^30.0.0",
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
import {
|
||||||
|
WebsocketBuilder,
|
||||||
|
ConstantBackoff,
|
||||||
|
ExponentialBackoff,
|
||||||
|
} from "websocket-ts";
|
||||||
import { MessageTypes } from "./common";
|
import { MessageTypes } from "./common";
|
||||||
import { packOutgoingPacket, unpackIncomingPacket } from "./messages/packet";
|
import { packOutgoingPacket, unpackIncomingPacket } from "./messages/packet";
|
||||||
import { packers, unpackers } from "./mapping";
|
import { packers, unpackers } from "./mapping";
|
||||||
@@ -8,135 +13,103 @@ export interface BenncClientOptions {
|
|||||||
url: string;
|
url: string;
|
||||||
protocols?: string[];
|
protocols?: string[];
|
||||||
autoReconnect?: boolean;
|
autoReconnect?: boolean;
|
||||||
|
reconnectBackoff?: "constant" | "linear" | "exponential";
|
||||||
reconnectDelay?: number;
|
reconnectDelay?: number;
|
||||||
maxReconnectAttempts?: number;
|
maxReconnectAttempts?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ConnectionState {
|
export class BenncClient extends EventTarget {
|
||||||
connected: boolean;
|
private ws: any = null;
|
||||||
connecting: boolean;
|
|
||||||
reconnectAttempts: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
type EventListener = (...args: any[]) => void;
|
|
||||||
|
|
||||||
export class BenncClient {
|
|
||||||
private ws: WebSocket | null = null;
|
|
||||||
private options: BenncClientOptions;
|
private options: BenncClientOptions;
|
||||||
private state: ConnectionState = {
|
|
||||||
connected: false,
|
|
||||||
connecting: false,
|
|
||||||
reconnectAttempts: 0,
|
|
||||||
};
|
|
||||||
private reconnectTimer: ReturnType<typeof setTimeout> | null = null;
|
|
||||||
private eventListeners: Map<string, EventListener[]> = new Map();
|
|
||||||
|
|
||||||
constructor(options: BenncClientOptions) {
|
constructor(options: BenncClientOptions) {
|
||||||
|
super();
|
||||||
this.options = {
|
this.options = {
|
||||||
autoReconnect: true,
|
autoReconnect: true,
|
||||||
reconnectDelay: 5000,
|
reconnectBackoff: "exponential",
|
||||||
|
reconnectDelay: 1000,
|
||||||
maxReconnectAttempts: 10,
|
maxReconnectAttempts: 10,
|
||||||
...options,
|
...options,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
on(event: string, listener: EventListener): void {
|
|
||||||
if (!this.eventListeners.has(event)) {
|
|
||||||
this.eventListeners.set(event, []);
|
|
||||||
}
|
|
||||||
this.eventListeners.get(event)!.push(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
off(event: string, listener: EventListener): void {
|
|
||||||
const listeners = this.eventListeners.get(event);
|
|
||||||
if (listeners) {
|
|
||||||
const index = listeners.indexOf(listener);
|
|
||||||
if (index !== -1) {
|
|
||||||
listeners.splice(index, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
emit(event: string, ...args: any[]): void {
|
|
||||||
const listeners = this.eventListeners.get(event);
|
|
||||||
if (listeners) {
|
|
||||||
listeners.forEach((listener) => listener(...args));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
connect(): Promise<void> {
|
connect(): Promise<void> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (this.state.connected || this.state.connecting) {
|
if (
|
||||||
|
this.ws &&
|
||||||
|
(this.ws.state === WebSocket.OPEN ||
|
||||||
|
this.ws.state === WebSocket.CONNECTING)
|
||||||
|
) {
|
||||||
resolve();
|
resolve();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.state.connecting = true;
|
const builder = new WebsocketBuilder(this.options.url);
|
||||||
this.emit("connecting");
|
|
||||||
|
|
||||||
try {
|
if (this.options.protocols) {
|
||||||
this.ws = new WebSocket(this.options.url, this.options.protocols);
|
builder.withProtocols(this.options.protocols);
|
||||||
this.ws.binaryType = "arraybuffer";
|
|
||||||
|
|
||||||
this.ws.onopen = () => {
|
|
||||||
this.state.connected = true;
|
|
||||||
this.state.connecting = false;
|
|
||||||
this.state.reconnectAttempts = 0;
|
|
||||||
this.emit("connected");
|
|
||||||
resolve();
|
|
||||||
};
|
|
||||||
|
|
||||||
this.ws.onmessage = (event) => {
|
|
||||||
this.handleMessage(event.data);
|
|
||||||
};
|
|
||||||
|
|
||||||
this.ws.onclose = (event) => {
|
|
||||||
this.state.connected = false;
|
|
||||||
this.state.connecting = false;
|
|
||||||
this.emit("disconnected", { code: event.code, reason: event.reason });
|
|
||||||
|
|
||||||
if (this.options.autoReconnect && this.shouldReconnect()) {
|
|
||||||
this.scheduleReconnect();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.ws.onerror = (error) => {
|
|
||||||
this.state.connecting = false;
|
|
||||||
this.emit("error", error);
|
|
||||||
reject(error);
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
this.state.connecting = false;
|
|
||||||
reject(error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.options.autoReconnect) {
|
||||||
|
let backoff;
|
||||||
|
switch (this.options.reconnectBackoff) {
|
||||||
|
case "constant":
|
||||||
|
backoff = new ConstantBackoff(this.options.reconnectDelay!);
|
||||||
|
break;
|
||||||
|
case "exponential":
|
||||||
|
default:
|
||||||
|
backoff = new ExponentialBackoff(this.options.reconnectDelay!);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
builder.withBackoff(backoff);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ws = builder
|
||||||
|
.onOpen(() => {
|
||||||
|
this.dispatchEvent(new CustomEvent("connected"));
|
||||||
|
resolve();
|
||||||
|
})
|
||||||
|
.onClose((_: any, event: any) => {
|
||||||
|
this.dispatchEvent(
|
||||||
|
new CustomEvent("disconnected", {
|
||||||
|
detail: { code: event.code, reason: event.reason },
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.onError((_: any, error: any) => {
|
||||||
|
this.dispatchEvent(new CustomEvent("error", { detail: error }));
|
||||||
|
reject(error);
|
||||||
|
})
|
||||||
|
.onMessage((_: any, event: any) => {
|
||||||
|
this.handleMessage(event.data);
|
||||||
|
})
|
||||||
|
.onReconnect(() => {
|
||||||
|
this.dispatchEvent(new CustomEvent("reconnecting"));
|
||||||
|
})
|
||||||
|
.build();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnect(): void {
|
disconnect(): void {
|
||||||
if (this.reconnectTimer) {
|
|
||||||
clearTimeout(this.reconnectTimer);
|
|
||||||
this.reconnectTimer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.ws) {
|
if (this.ws) {
|
||||||
this.ws.close();
|
this.ws.close();
|
||||||
this.ws = null;
|
this.ws = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.state.connected = false;
|
|
||||||
this.state.connecting = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isConnected(): boolean {
|
isConnected(): boolean {
|
||||||
return this.state.connected;
|
return this.ws && this.ws.state === WebSocket.OPEN;
|
||||||
}
|
}
|
||||||
|
|
||||||
getConnectionState(): ConnectionState {
|
getConnectionState(): { connected: boolean; connecting: boolean } {
|
||||||
return { ...this.state };
|
return {
|
||||||
|
connected: this.ws?.state === WebSocket.OPEN || false,
|
||||||
|
connecting: this.ws?.state === WebSocket.CONNECTING || false,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private sendRawMessage(messageType: number, data?: Uint8Array): void {
|
private sendRawMessage(messageType: number, data?: Uint8Array): void {
|
||||||
if (!this.state.connected || !this.ws) {
|
if (!this.isConnected()) {
|
||||||
throw new Error("Client is not connected");
|
throw new Error("Client is not connected");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,46 +178,34 @@ export class BenncClient {
|
|||||||
private handleMessage(data: ArrayBuffer): void {
|
private handleMessage(data: ArrayBuffer): void {
|
||||||
try {
|
try {
|
||||||
const packet = unpackIncomingPacket(data);
|
const packet = unpackIncomingPacket(data);
|
||||||
this.emit("packet", packet);
|
this.dispatchEvent(new CustomEvent("packet", { detail: packet }));
|
||||||
|
|
||||||
const unpacker = unpackers[packet.messageType as keyof typeof unpackers];
|
const unpacker = unpackers[packet.messageType as keyof typeof unpackers];
|
||||||
if (unpacker) {
|
if (unpacker) {
|
||||||
const unpackedData = unpacker(packet.data);
|
const unpackedData = unpacker(packet.data);
|
||||||
this.emit("message", {
|
const messageEvent = {
|
||||||
messageType: packet.messageType,
|
messageType: packet.messageType,
|
||||||
senderId: packet.senderId,
|
senderId: packet.senderId,
|
||||||
data: unpackedData,
|
data: unpackedData,
|
||||||
});
|
};
|
||||||
this.emit(`message:${packet.messageType}`, {
|
this.dispatchEvent(
|
||||||
senderId: packet.senderId,
|
new CustomEvent("message", { detail: messageEvent }),
|
||||||
data: unpackedData,
|
);
|
||||||
});
|
this.dispatchEvent(
|
||||||
|
new CustomEvent(`message:${packet.messageType}`, {
|
||||||
|
detail: {
|
||||||
|
senderId: packet.senderId,
|
||||||
|
data: unpackedData,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
this.emit("unknown-message", packet);
|
this.dispatchEvent(
|
||||||
|
new CustomEvent("unknown-message", { detail: packet }),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.emit("parse-error", error);
|
this.dispatchEvent(new CustomEvent("parse-error", { detail: error }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private shouldReconnect(): boolean {
|
|
||||||
const maxAttempts = this.options.maxReconnectAttempts || 10;
|
|
||||||
return this.state.reconnectAttempts < maxAttempts;
|
|
||||||
}
|
|
||||||
|
|
||||||
private scheduleReconnect(): void {
|
|
||||||
const delay = this.options.reconnectDelay || 5000;
|
|
||||||
this.reconnectTimer = setTimeout(() => {
|
|
||||||
this.state.reconnectAttempts++;
|
|
||||||
this.emit("reconnecting", this.state.reconnectAttempts);
|
|
||||||
this.connect().catch((error) => {
|
|
||||||
this.emit("reconnect-failed", error);
|
|
||||||
if (this.shouldReconnect()) {
|
|
||||||
this.scheduleReconnect();
|
|
||||||
} else {
|
|
||||||
this.emit("reconnect-exhausted");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, delay);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,4 +8,4 @@ export type { BasicMessage } from "./messages/basic";
|
|||||||
export type { UserDataRequestMessage } from "./messages/userDataRequest";
|
export type { UserDataRequestMessage } from "./messages/userDataRequest";
|
||||||
export type { UserDataResponseMessage } from "./messages/userDataResponse";
|
export type { UserDataResponseMessage } from "./messages/userDataResponse";
|
||||||
export { BenncClient } from "./BenncClient";
|
export { BenncClient } from "./BenncClient";
|
||||||
export type { BenncClientOptions, ConnectionState } from "./BenncClient";
|
export type { BenncClientOptions } from "./BenncClient";
|
||||||
|
|||||||
Reference in New Issue
Block a user