Skip to content

Commit

Permalink
Documentation / Improvements (#7)
Browse files Browse the repository at this point in the history
* Renamed Builder to WsBuilder

* Updated README.md

* Only retry connection when using Backoff

* Improved documentation

* Updated README.md

* Release version 1.0.1
  • Loading branch information
jjxxs authored Sep 15, 2020
1 parent a4ebb94 commit 9435187
Show file tree
Hide file tree
Showing 8 changed files with 64 additions and 50 deletions.
61 changes: 35 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,67 +11,76 @@ A client-websocket written in TypeScript to be used from within browsers with fo
- Uses the browser-native WebSocket-functionality
- Copies the event-based WebSocket-API
- Provides low-level access to the underlying WebSocket if needed
- Optional automatic reconnects
- Optionally automatic reconnect when disconnected
- With easy-to-configure parameters (time between retries)
- Optional pending-messages
- Optionally buffer messages while disconnected
- With easy-to-configure buffers (size, behaviour)
- Builder-class for easy initialization and configuration

## Usage
New instances can be easily created through the Builder-class.
New instances can be created with the Builder.

```typescript
const ws = new Builder('ws://localhost:42421').build();
const ws = new WsBuilder('ws://localhost:42421').build();
```

#### Callbacks
You can register callbacks for `onOpen`-, `onClose`-, `onError`- and `onMessage`-events. The callbacks get called with the websocket-instance that caused the event plus the event as parameters.
You can register callbacks for `onOpen`-, `onClose`-, `onError`- and `onMessage`-events. The callbacks get called with the websocket-instance plus the event itself as parameters.
```typescript
const ws = new Builder('ws://localhost:42421')
.onOpen((i, e) => { console.log("opened") })
.onClose((i, e) => { console.log("closed") })
.onError((i, e) => { console.log("error") })
.onMessage((i, e) => { i.send(e.data) })
const ws = new WsBuilder('ws://localhost:42421')
.onOpen((ws, e) => { console.log("opened") })
.onClose((ws, e) => { console.log("closed") })
.onError((ws, e) => { console.log("error") })
.onMessage((ws, e) => { ws.send(e.data) })
.build();
```

It is possible to register multiple callbacks for the same event, they are called in stack-order:
```typescript
const ws = new Builder('ws://localhost:42421')
.onMessage((i, e) => { console.log("sent echo") })
.onMessage((i, e) => { i.send(e.data) })
.onMessage((i, e) => { console.log("message received") })
const ws = new WsBuilder('ws://localhost:42421')
.onMessage((ws, e) => { console.log("sent echo") })
.onMessage((ws, e) => { i.send(e.data) })
.onMessage((ws, e) => { console.log("message received") })
.build();
```

#### Buffer
To buffer pending messages while your websocket is disconnected, configure it to use a Buffer. These pending messages
will be sent out as soon as the connection is (re)-established.
To buffer pending messages while your websocket is disconnected, configure it to use a ```Buffer```. While disconnected,
calls to the `send()`-method will write the message you want to send to the buffer. These pending messages
will be sent out in the order that they were inserted as soon as the connection is (re)-established.

```typescript
const ws = new Builder('ws://localhost:42421')
.withBuffer(new LRUBuffer(100)) // buffers up to 100 messages, substitutes old messages with new ones
// LRUBuffer with a capacity of 1000 messages. If the buffer is full,
// the oldest message will be replaced by the newest and so on.
const ws = new WsBuilder('ws://localhost:42421')
.withBuffer(new LRUBuffer(1000))
.build();
```

```typescript
const ws = new Builder('ws://localhost:42421')
.withBuffer(new TimeBuffer(5 * 60 * 1000)) // buffers messages that were written within the last 5 minutes
// TimeBuffer keeping all messages from the last five minutes,
// older messages are dropped.
const ws = new WsBuilder('ws://localhost:42421')
.withBuffer(new TimeBuffer(5 * 60 * 1000))
.build();
```

#### Reconnect / Backoff
To configure the websocket to automatically reconnect when the connection gets lost, provide it with a Backoff.
The type of backoff provided decides the delay between connection-retries.
When provided with a ```Backoff```, the websocket will automatically try to reconnect when the connection got lost. The
type of backoff provided dictates the delay between connection-retries in milliseconds.

```typescript
const ws = new Builder('ws://localhost:42421')
.withBackoff(new ConstantBackoff(500)) // Always waits 500 ms between retries
// ConstantBackoff will wait a fixed time between connection-retries.
const ws = new WsBuilder('ws://localhost:42421')
.withBackoff(new ConstantBackoff(500))
.build();
```

```typescript
const ws = new Builder('ws://localhost:42421')
.withBackoff(new ExponentialBackoff(100)) // Doubles the time between reconnects with every try
// ExponentialBackoff will double the time to wait between retries with
// every unsuccessful retry until a maximum is reached. This one goes from
// 100 * 2^0 to 100 * 2^5, so [100, 200, 400, 800, 1600, 3200] milliseconds.
const ws = new WsBuilder('ws://localhost:42421')
.withBackoff(new ExponentialBackoff(100, 0, 5))
.build();
```
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "websocket-ts",
"version": "1.0.0-rc5",
"version": "1.0.1",
"main": "lib/index.js",
"types": "lib/",
"license": "MIT",
Expand Down
3 changes: 3 additions & 0 deletions src/buffer/timebuffer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import {Buffer} from "./buffer";
* within maxAge milliseconds. E.g. to only keep items in the
* buffer that are less than a minute old, create the buffer with
* a maximum age of 60.000.
*
* When reading from the TimeBuffer, elements will be returned
* in FIFO-order (queue).
*/
export class TimeBuffer<E> implements Buffer<E> {
private readonly maxAge: number;
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ export * from './backoff/backoff';
export * from './buffer/buffer';
export * from './buffer/lrubuffer'
export * from './websocket';
export * from './builder';
export * from './wsbuilder';
12 changes: 7 additions & 5 deletions src/websocket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ interface WebsocketEventMap {
open: Event;
}

export type Listeners = {
type Listeners = {
open: eventListener<WebsocketEvents.open>[];
close: eventListener<WebsocketEvents.close>[];
error: eventListener<WebsocketEvents.error>[];
Expand Down Expand Up @@ -90,7 +90,7 @@ export class Websocket {
if (l.options !== undefined && (l.options as AddEventListenerOptions).once)
remove.push(l);
if (l.options !== undefined && (l.options as AddEventListenerOptions).passive && ev.defaultPrevented)
console.log("default was prevent when listener was market as passive");
console.log("default was prevent when listener was marked as passive");
}
const listeners = this.listeners[type] as eventListener<K>[];
listeners.forEach(l => dispatch(l));
Expand All @@ -110,7 +110,7 @@ export class Websocket {
const wsIsClosed = this.websocket?.readyState == this.websocket?.CLOSED;
const wsIsClosing = this.websocket?.readyState === this.websocket?.CLOSING;
if (!this.closedByUser && (wsIsClosed || wsIsClosing)) {
this.reconnect();
this.reconnectWithBackoff();
}
if (type === WebsocketEvents.open) {
if (this.timer !== undefined)
Expand All @@ -122,8 +122,10 @@ export class Websocket {
this.dispatchEvent<K>(type, ev);
}

private reconnect() {
const backoff = this.backoff?.Next() || 0;
private reconnectWithBackoff() {
if (this.backoff === undefined)
return;
const backoff = this.backoff.Next() || 0;
this.timer = setTimeout(() => {
this.tryConnect();
}, backoff);
Expand Down
16 changes: 8 additions & 8 deletions src/builder.ts → src/wsbuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {Backoff} from "./backoff/backoff";
import {Buffer} from "./buffer/buffer";
import {Websocket, WebsocketEvents} from "./websocket";

export class Builder {
export class WsBuilder {
private readonly url: string;
private protocols?: string | string[]
private backoff?: Backoff
Expand All @@ -16,22 +16,22 @@ export class Builder {
this.url = url;
}

public withProtocols(p: string | string[]): Builder {
public withProtocols(p: string | string[]): WsBuilder {
this.protocols = p;
return this;
}

public withBackoff(backoff: Backoff): Builder {
public withBackoff(backoff: Backoff): WsBuilder {
this.backoff = backoff;
return this;
}

public withBuffer(buffer: Buffer<any>): Builder {
public withBuffer(buffer: Buffer<any>): WsBuilder {
this.buffer = buffer;
return this;
}

public onOpen(fn: (instance: Websocket, ev: Event) => any): Builder {
public onOpen(fn: (instance: Websocket, ev: Event) => any): WsBuilder {
const onOpen = this.onOpenChain;
this.onOpenChain = (instance: Websocket, ev2: Event) => {
fn(instance, ev2);
Expand All @@ -41,7 +41,7 @@ export class Builder {
return this;
}

public onClose(fn: (instance: Websocket, ev: CloseEvent) => any): Builder {
public onClose(fn: (instance: Websocket, ev: CloseEvent) => any): WsBuilder {
const onClose = this.onCloseChain;
this.onCloseChain = (instance: Websocket, ev2: CloseEvent) => {
fn(instance, ev2);
Expand All @@ -51,7 +51,7 @@ export class Builder {
return this;
}

public onError(fn: (instance: Websocket, ev: Event) => any): Builder {
public onError(fn: (instance: Websocket, ev: Event) => any): WsBuilder {
const onError = this.onErrorChain;
this.onErrorChain = (instance: Websocket, ev2: Event) => {
fn(instance, ev2);
Expand All @@ -61,7 +61,7 @@ export class Builder {
return this;
}

public onMessage(fn: (instance: Websocket, ev: MessageEvent) => any): Builder {
public onMessage(fn: (instance: Websocket, ev: MessageEvent) => any): WsBuilder {
const onMessage = this.onMessageChain;
this.onMessageChain = (instance: Websocket, ev2: MessageEvent) => {
fn(instance, ev2);
Expand Down
6 changes: 3 additions & 3 deletions test/builder.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import {Builder, LRUBuffer, Websocket} from "../src";
import {WsBuilder, LRUBuffer, Websocket} from "../src";
import {ConstantBackoff} from "../src/backoff/constantbackoff";

describe("Testsuite for Builder", () => {
const url = "ws://localhost:42421";
let builder: Builder
let builder: WsBuilder

beforeEach(() => {
builder = new Builder(url);
builder = new WsBuilder(url);
});

test("Builder should set protocols", () => {
Expand Down
12 changes: 6 additions & 6 deletions test/websocket.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {Websocket, WebsocketEvents} from "../src/websocket";
import {Builder} from "../src/builder";
import {Websocket, WebsocketEvents} from "../src";
import {WsBuilder} from "../src";
import {Server} from "ws";

describe("Testsuite for Websocket events", () => {
Expand Down Expand Up @@ -27,7 +27,7 @@ describe("Testsuite for Websocket events", () => {

test("Websocket event-onOpen", async () => {
await new Promise<wsWithEv<Event>>(resolve => {
ws = new Builder(url).onOpen((instance, ev) => {
ws = new WsBuilder(url).onOpen((instance, ev) => {
resolve({instance, ev});
}).build();
}).then(e => {
Expand All @@ -38,7 +38,7 @@ describe("Testsuite for Websocket events", () => {

test("Websocket event-onClose", async () => {
await new Promise<wsWithEv<CloseEvent>>(resolve => {
ws =new Builder(url).onClose((instance, ev) => {
ws =new WsBuilder(url).onClose((instance, ev) => {
resolve({instance, ev});
}).build();
wss?.close();
Expand All @@ -51,7 +51,7 @@ describe("Testsuite for Websocket events", () => {
test("Websocket event-onError", async () => {
await new Promise<wsWithEv<Event>>(resolve => {
wss?.close();
ws = new Builder(url).onError((instance, ev) => {
ws = new WsBuilder(url).onError((instance, ev) => {
resolve({instance, ev});
}).build();
}).then(e => {
Expand All @@ -63,7 +63,7 @@ describe("Testsuite for Websocket events", () => {
test("Websocket event-onMessage", async () => {
const testMsg = "this is a test-message!";
const onMessagePromise = new Promise<wsWithEv<MessageEvent>>(resolve => {
ws = new Builder(url).onMessage((instance, ev) => {
ws = new WsBuilder(url).onMessage((instance, ev) => {
resolve({instance, ev});
}).build();
}).then(e => {
Expand Down

0 comments on commit 9435187

Please sign in to comment.