@d-fischer/connection
Advanced tools
Comparing version
import type { Logger } from '@d-fischer/logger'; | ||
import { EventEmitter } from '@d-fischer/typed-event-emitter'; | ||
import type { Connection, ConnectionOptions } from './Connection'; | ||
export declare type InferConnectionOptions<T extends Connection> = T extends AbstractConnection<infer O> ? O : never; | ||
export type InferConnectionOptions<T extends Connection> = T extends AbstractConnection<infer O> ? O : never; | ||
export declare abstract class AbstractConnection<Options = never> extends EventEmitter implements Connection { | ||
@@ -20,4 +20,4 @@ private readonly _lineBased; | ||
sendLine(line: string): void; | ||
abstract connect(): Promise<void>; | ||
abstract disconnect(): Promise<void>; | ||
abstract connect(): void; | ||
abstract disconnect(): void; | ||
assumeExternalDisconnect(): void; | ||
@@ -24,0 +24,0 @@ protected receiveRaw(data: string): void; |
@@ -11,4 +11,4 @@ import type { Logger } from '@d-fischer/logger'; | ||
readonly onEnd: EventBinder<[boolean, Error?]>; | ||
connect: () => Promise<void>; | ||
disconnect: () => Promise<void>; | ||
connect: () => void; | ||
disconnect: () => void; | ||
assumeExternalDisconnect: () => void; | ||
@@ -15,0 +15,0 @@ sendLine: (line: string) => void; |
@@ -1,9 +0,17 @@ | ||
import { AbstractConnection } from './AbstractConnection'; | ||
import type { ConnectionOptions, ConnectionTarget } from './Connection'; | ||
export declare class DirectConnection extends AbstractConnection { | ||
constructor(target: ConnectionTarget, options: ConnectionOptions<never>); | ||
get hasSocket(): boolean; | ||
sendRaw(line: string): void; | ||
connect(): Promise<void>; | ||
disconnect(): Promise<void>; | ||
import { type EventBinder } from '@d-fischer/typed-event-emitter'; | ||
import { type Connection } from './Connection'; | ||
export declare class DirectConnection implements Connection { | ||
readonly hasSocket: boolean; | ||
sendRaw: (line: string) => void; | ||
sendLine: (line: string) => void; | ||
readonly isConnected: boolean; | ||
readonly isConnecting: boolean; | ||
readonly connect: () => void; | ||
readonly disconnect: () => void; | ||
readonly assumeExternalDisconnect: () => void; | ||
readonly onConnect: EventBinder<[]>; | ||
readonly onDisconnect: EventBinder<[boolean, Error]>; | ||
readonly onEnd: EventBinder<[boolean, Error]>; | ||
readonly onReceive: EventBinder<[string]>; | ||
constructor(); | ||
} |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.DirectConnection = void 0; | ||
const AbstractConnection_1 = require("./AbstractConnection"); | ||
class DirectConnection extends AbstractConnection_1.AbstractConnection { | ||
constructor(target, options) { | ||
class DirectConnection { | ||
constructor() { | ||
throw new Error('DirectConnection is not implemented in a browser environment'); | ||
super(options); | ||
} | ||
// eslint-disable-next-line @typescript-eslint/class-literal-property-style | ||
get hasSocket() { | ||
return false; | ||
} | ||
sendRaw(line) { | ||
void line; | ||
} | ||
async connect() { | ||
// | ||
} | ||
async disconnect() { | ||
// | ||
} | ||
} | ||
exports.DirectConnection = DirectConnection; | ||
//# sourceMappingURL=DirectConnection-stub.js.map |
@@ -11,4 +11,4 @@ import { AbstractConnection } from './AbstractConnection'; | ||
sendRaw(line: string): void; | ||
connect(): Promise<void>; | ||
disconnect(): Promise<void>; | ||
connect(): void; | ||
disconnect(): void; | ||
} |
@@ -26,69 +26,54 @@ "use strict"; | ||
} | ||
async connect() { | ||
connect() { | ||
var _a; | ||
(_a = this._logger) === null || _a === void 0 ? void 0 : _a.trace('DirectConnection connect'); | ||
await new Promise((resolve, reject) => { | ||
this._connecting = true; | ||
if (this._secure) { | ||
this._socket = tls.connect(this._port, this._host); | ||
} | ||
else { | ||
this._socket = new net_1.Socket(); | ||
this._socket.connect(this._port, this._host); | ||
} | ||
this._socket.on('connect', () => { | ||
var _a; | ||
(_a = this._logger) === null || _a === void 0 ? void 0 : _a.trace('DirectConnection onConnect'); | ||
this._connecting = false; | ||
this._connected = true; | ||
this.emit(this.onConnect); | ||
resolve(); | ||
}); | ||
this._socket.on('error', (err) => { | ||
var _a; | ||
(_a = this._logger) === null || _a === void 0 ? void 0 : _a.trace(`DirectConnection onError message:${err.message}`); | ||
this._connecting = true; | ||
if (this._secure) { | ||
this._socket = tls.connect(this._port, this._host); | ||
} | ||
else { | ||
this._socket = new net_1.Socket(); | ||
this._socket.connect(this._port, this._host); | ||
} | ||
this._socket.on('connect', () => { | ||
var _a; | ||
(_a = this._logger) === null || _a === void 0 ? void 0 : _a.trace('DirectConnection onConnect'); | ||
this._connecting = false; | ||
this._connected = true; | ||
this.emit(this.onConnect); | ||
}); | ||
this._socket.on('error', (err) => { | ||
var _a; | ||
(_a = this._logger) === null || _a === void 0 ? void 0 : _a.trace(`DirectConnection onError message:${err.message}`); | ||
this._connected = false; | ||
this._connecting = false; | ||
this.emit(this.onDisconnect, false, err); | ||
}); | ||
this._socket.on('data', (data) => { | ||
this.receiveRaw(data.toString()); | ||
}); | ||
this._socket.on('close', (hadError) => { | ||
var _a; | ||
(_a = this._logger) === null || _a === void 0 ? void 0 : _a.trace(`DirectConnection onClose hadError:${hadError.toString()}`); | ||
if (!hadError) { | ||
this._connected = false; | ||
this._connecting = false; | ||
this.emit(this.onDisconnect, false, err); | ||
reject(err); | ||
}); | ||
this._socket.on('data', (data) => { | ||
this.receiveRaw(data.toString()); | ||
}); | ||
this._socket.on('close', (hadError) => { | ||
var _a; | ||
(_a = this._logger) === null || _a === void 0 ? void 0 : _a.trace(`DirectConnection onClose hadError:${hadError.toString()}`); | ||
if (!hadError) { | ||
this._connected = false; | ||
this._connecting = false; | ||
this.emit(this.onDisconnect, true); | ||
} | ||
if (this._socket) { | ||
this._socket.removeAllListeners('connect'); | ||
this._socket.removeAllListeners('error'); | ||
this._socket.removeAllListeners('data'); | ||
this._socket.removeAllListeners('close'); | ||
this._socket = null; | ||
} | ||
}); | ||
}); | ||
} | ||
async disconnect() { | ||
var _a; | ||
(_a = this._logger) === null || _a === void 0 ? void 0 : _a.trace('DirectConnection disconnect'); | ||
await new Promise(resolve => { | ||
this.emit(this.onDisconnect, true); | ||
} | ||
if (this._socket) { | ||
const listener = this.onDisconnect(() => { | ||
listener.unbind(); | ||
resolve(); | ||
}); | ||
this._socket.end(); | ||
this._socket.removeAllListeners('connect'); | ||
this._socket.removeAllListeners('error'); | ||
this._socket.removeAllListeners('data'); | ||
this._socket.removeAllListeners('close'); | ||
this._socket = null; | ||
} | ||
else { | ||
resolve(); | ||
} | ||
}); | ||
} | ||
disconnect() { | ||
var _a, _b; | ||
(_a = this._logger) === null || _a === void 0 ? void 0 : _a.trace('DirectConnection disconnect'); | ||
(_b = this._socket) === null || _b === void 0 ? void 0 : _b.end(); | ||
} | ||
} | ||
exports.DirectConnection = DirectConnection; | ||
//# sourceMappingURL=DirectConnection.js.map |
@@ -1,2 +0,2 @@ | ||
import type { Constructor } from '@d-fischer/shared-utils'; | ||
import type { Constructor, ResolvableValueSync } from '@d-fischer/shared-utils'; | ||
import { EventEmitter } from '@d-fischer/typed-event-emitter'; | ||
@@ -8,2 +8,3 @@ import type { InferConnectionOptions } from './AbstractConnection'; | ||
initialRetryLimit?: number; | ||
overlapManualReconnect?: boolean; | ||
} | ||
@@ -21,2 +22,3 @@ export declare class PersistentConnection<T extends Connection> extends EventEmitter implements Connection { | ||
private _currentConnection?; | ||
private _previousConnection?; | ||
readonly onReceive: import("@d-fischer/typed-event-emitter").EventBinder<[string]>; | ||
@@ -26,3 +28,3 @@ readonly onConnect: import("@d-fischer/typed-event-emitter").EventBinder<[]>; | ||
readonly onEnd: import("@d-fischer/typed-event-emitter").EventBinder<[boolean, (Error | undefined)?]>; | ||
constructor(_type: Constructor<T>, _target: ConnectionTarget, _config?: PersistentConnectionConfig<InferConnectionOptions<T>>); | ||
constructor(_type: Constructor<T>, _target: ResolvableValueSync<ConnectionTarget>, _config?: PersistentConnectionConfig<InferConnectionOptions<T>>); | ||
get isConnected(): boolean; | ||
@@ -32,9 +34,10 @@ get isConnecting(): boolean; | ||
sendLine(line: string): void; | ||
connect(): Promise<void>; | ||
disconnect(): Promise<void>; | ||
connect(): void; | ||
disconnect(): void; | ||
assumeExternalDisconnect(): void; | ||
reconnect(): Promise<void>; | ||
private _connect; | ||
reconnect(): void; | ||
acknowledgeSuccessfulReconnect(): void; | ||
private _startTryingToConnect; | ||
private _tryConnect; | ||
private _reconnect; | ||
private static _getReconnectWaitTime; | ||
} |
@@ -40,6 +40,11 @@ "use strict"; | ||
} | ||
async connect() { | ||
await this._connect(true); | ||
connect() { | ||
if (this._currentConnection || this._connecting) { | ||
throw new Error('Connection already present'); | ||
} | ||
this._connecting = true; | ||
this._connectionRetryCount = 0; | ||
this._tryConnect(true); | ||
} | ||
async disconnect() { | ||
disconnect() { | ||
var _a; | ||
@@ -51,3 +56,3 @@ (_a = this._logger) === null || _a === void 0 ? void 0 : _a.trace(`PersistentConnection disconnect currentConnectionExists:${Boolean(this._currentConnection).toString()} connecting:${this._connecting.toString()}`); | ||
this._currentConnection = undefined; | ||
await lastConnection.disconnect(); | ||
lastConnection.disconnect(); | ||
} | ||
@@ -60,45 +65,48 @@ } | ||
} | ||
async reconnect() { | ||
await this._reconnect(true); | ||
reconnect() { | ||
this._reconnect(true); | ||
} | ||
async _connect(userGenerated = false) { | ||
var _a, _b, _c, _d; | ||
(_a = this._logger) === null || _a === void 0 ? void 0 : _a.trace(`PersistentConnection connect currentConnectionExists:${Boolean(this._currentConnection).toString()} connecting:${this._connecting.toString()}`); | ||
if (this._currentConnection || this._connecting) { | ||
throw new Error('Connection already present'); | ||
acknowledgeSuccessfulReconnect() { | ||
if (this._previousConnection) { | ||
this._previousConnection.disconnect(); | ||
this._previousConnection = undefined; | ||
} | ||
} | ||
_startTryingToConnect(userGenerated = false) { | ||
this._connecting = true; | ||
this._connectionRetryCount = 0; | ||
this._connecting = true; | ||
this._tryConnect(userGenerated); | ||
} | ||
_tryConnect(userGenerated = false) { | ||
var _a, _b; | ||
(_a = this._logger) === null || _a === void 0 ? void 0 : _a.trace(`PersistentConnection tryConnect currentConnectionExists:${Boolean(this._currentConnection).toString()} connecting:${this._connecting.toString()}`); | ||
const retryLimit = userGenerated ? this._initialRetryLimit : this._retryLimit; | ||
this._retryTimerGenerator = PersistentConnection._getReconnectWaitTime(); | ||
while (this._connectionRetryCount <= retryLimit) { | ||
const newConnection = (this._currentConnection = new this._type(this._target, this._config)); | ||
newConnection.onReceive(line => this.emit(this.onReceive, line)); | ||
newConnection.onConnect(() => this.emit(this.onConnect)); | ||
newConnection.onDisconnect((manually, reason) => { | ||
this.emit(this.onDisconnect, manually, reason); | ||
if (manually) { | ||
this.emit(this.onEnd, true); | ||
void newConnection.disconnect(); | ||
if (this._currentConnection === newConnection) { | ||
this._currentConnection = undefined; | ||
} | ||
(_b = this._retryTimerGenerator) !== null && _b !== void 0 ? _b : (this._retryTimerGenerator = (0, shared_utils_1.fibWithLimit)(120)); | ||
const newConnection = (this._currentConnection = new this._type((0, shared_utils_1.resolveConfigValueSync)(this._target), this._config)); | ||
newConnection.onReceive(line => this.emit(this.onReceive, line)); | ||
newConnection.onConnect(() => { | ||
this.emit(this.onConnect); | ||
this._connecting = false; | ||
this._retryTimerGenerator = undefined; | ||
}); | ||
newConnection.onDisconnect((manually, reason) => { | ||
var _a, _b, _c; | ||
this.emit(this.onDisconnect, manually, reason); | ||
if (manually) { | ||
this.emit(this.onEnd, true); | ||
this._connecting = false; | ||
this._retryTimerGenerator = undefined; | ||
newConnection.disconnect(); | ||
if (this._currentConnection === newConnection) { | ||
this._currentConnection = undefined; | ||
} | ||
else if (!this._connecting) { | ||
void this._reconnect(); | ||
if (this._previousConnection === newConnection) { | ||
this._previousConnection = undefined; | ||
} | ||
}); | ||
try { | ||
await newConnection.connect(); | ||
this._connecting = false; | ||
return; | ||
} | ||
catch (e) { | ||
if (!this._connecting) { | ||
else if (this._connecting) { | ||
(_a = this._logger) === null || _a === void 0 ? void 0 : _a.debug(`Connection error caught: ${(_b = reason === null || reason === void 0 ? void 0 : reason.message) !== null && _b !== void 0 ? _b : 'unknown error'}`); | ||
if (this._connectionRetryCount >= retryLimit) { | ||
return; | ||
} | ||
(_b = this._logger) === null || _b === void 0 ? void 0 : _b.debug(`Connection error caught: ${e.message}`); | ||
if (this._connectionRetryCount >= retryLimit) { | ||
break; | ||
} | ||
this._connectionRetryCount++; | ||
@@ -109,31 +117,26 @@ const secs = this._retryTimerGenerator.next().value; | ||
} | ||
await (0, shared_utils_1.delay)(secs * 1000); | ||
(_d = this._logger) === null || _d === void 0 ? void 0 : _d.info(userGenerated ? 'Retrying connection' : 'Trying to reconnect'); | ||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition | ||
if (!this._connecting) { | ||
return; | ||
} | ||
setTimeout(() => { | ||
var _a; | ||
if (!this._connecting) { | ||
return; | ||
} | ||
(_a = this._logger) === null || _a === void 0 ? void 0 : _a.info(userGenerated ? 'Retrying connection' : 'Trying to reconnect'); | ||
this._tryConnect(); | ||
}, secs * 1000); | ||
} | ||
} | ||
const error = new Error(`Connection failed after trying ${retryLimit} times`); | ||
this.emit(this.onEnd, false, error); | ||
if (userGenerated) { | ||
throw error; | ||
} | ||
else { | ||
this._reconnect(); | ||
} | ||
}); | ||
newConnection.connect(); | ||
} | ||
async _reconnect(userGenerated = false) { | ||
void this.disconnect().catch((e) => { var _a; return (_a = this._logger) === null || _a === void 0 ? void 0 : _a.error(`Error while disconnecting for the reconnect: ${e.message}`); }); | ||
await this._connect(userGenerated); | ||
} | ||
// yes, this is just fibonacci with a limit | ||
static *_getReconnectWaitTime() { | ||
let current = 0; | ||
let next = 1; | ||
while (current < 120) { | ||
yield current; | ||
[current, next] = [next, current + next]; | ||
_reconnect(userGenerated = false) { | ||
if (userGenerated && this._config.overlapManualReconnect) { | ||
this._previousConnection = this._currentConnection; | ||
this._currentConnection = undefined; | ||
} | ||
while (true) { | ||
yield 120; | ||
else { | ||
this.disconnect(); | ||
} | ||
this._startTryingToConnect(userGenerated); | ||
} | ||
@@ -140,0 +143,0 @@ } |
@@ -14,4 +14,4 @@ /// <reference types="ws" /> | ||
sendRaw(line: string): void; | ||
connect(): Promise<void>; | ||
disconnect(): Promise<void>; | ||
connect(): void; | ||
disconnect(): void; | ||
} |
@@ -27,71 +27,53 @@ "use strict"; | ||
} | ||
async connect() { | ||
var _a; | ||
connect() { | ||
var _a, _b; | ||
(_a = this._logger) === null || _a === void 0 ? void 0 : _a.trace('WebSocketConnection connect'); | ||
await new Promise((resolve, reject) => { | ||
this._connecting = true; | ||
this._socket = new isomorphic_ws_1.WebSocket(this._url, (_b = this._additionalOptions) === null || _b === void 0 ? void 0 : _b.wsOptions); | ||
this._socket.onopen = () => { | ||
var _a; | ||
this._connecting = true; | ||
this._socket = new isomorphic_ws_1.WebSocket(this._url, (_a = this._additionalOptions) === null || _a === void 0 ? void 0 : _a.wsOptions); | ||
this._socket.onopen = () => { | ||
var _a; | ||
(_a = this._logger) === null || _a === void 0 ? void 0 : _a.trace('WebSocketConnection onOpen'); | ||
this._connected = true; | ||
this._connecting = false; | ||
this.emit(this.onConnect); | ||
resolve(); | ||
}; | ||
this._socket.onmessage = ({ data }) => { | ||
this.receiveRaw(data.toString()); | ||
}; | ||
// The following empty error callback needs to exist so connection errors are passed down to `onclose` down below - otherwise the process just crashes instead | ||
this._socket.onerror = e => { | ||
var _a; | ||
(_a = this._logger) === null || _a === void 0 ? void 0 : _a.trace(`WebSocketConnection onError message:${e.message}`); | ||
}; | ||
this._socket.onclose = e => { | ||
var _a; | ||
const wasConnected = this._connected; | ||
(_a = this._logger) === null || _a === void 0 ? void 0 : _a.trace(`WebSocketConnection onClose wasConnected:${wasConnected.toString()} wasClean:${e.wasClean.toString()}`); | ||
this._connected = false; | ||
this._connecting = false; | ||
if (e.wasClean) { | ||
this.emit(this.onDisconnect, true); | ||
this.emit(this.onEnd, true); | ||
} | ||
else { | ||
const err = new Error(`[${e.code}] ${e.reason}`); | ||
this.emit(this.onDisconnect, false, err); | ||
this.emit(this.onEnd, false, err); | ||
if (!wasConnected) { | ||
reject(err); | ||
} | ||
} | ||
if (this._socket) { | ||
this._socket.onopen = null; | ||
this._socket.onmessage = null; | ||
this._socket.onerror = null; | ||
this._socket.onclose = null; | ||
this._socket = null; | ||
} | ||
}; | ||
}); | ||
} | ||
async disconnect() { | ||
var _a; | ||
(_a = this._logger) === null || _a === void 0 ? void 0 : _a.trace('WebSocketConnection disconnect'); | ||
await new Promise(resolve => { | ||
if (this._socket) { | ||
const listener = this.onDisconnect(() => { | ||
listener.unbind(); | ||
resolve(); | ||
}); | ||
this._socket.close(); | ||
(_a = this._logger) === null || _a === void 0 ? void 0 : _a.trace('WebSocketConnection onOpen'); | ||
this._connected = true; | ||
this._connecting = false; | ||
this.emit(this.onConnect); | ||
}; | ||
this._socket.onmessage = ({ data }) => { | ||
this.receiveRaw(data.toString()); | ||
}; | ||
// The following empty error callback needs to exist so connection errors are passed down to `onclose` down below - otherwise the process just crashes instead | ||
this._socket.onerror = e => { | ||
var _a; | ||
(_a = this._logger) === null || _a === void 0 ? void 0 : _a.trace(`WebSocketConnection onError message:${e.message}`); | ||
}; | ||
this._socket.onclose = e => { | ||
var _a; | ||
const wasConnected = this._connected; | ||
(_a = this._logger) === null || _a === void 0 ? void 0 : _a.trace(`WebSocketConnection onClose wasConnected:${wasConnected.toString()} wasClean:${e.wasClean.toString()}`); | ||
this._connected = false; | ||
this._connecting = false; | ||
if (e.wasClean) { | ||
this.emit(this.onDisconnect, true); | ||
this.emit(this.onEnd, true); | ||
} | ||
else { | ||
resolve(); | ||
const err = new Error(`[${e.code}] ${e.reason}`); | ||
this.emit(this.onDisconnect, false, err); | ||
this.emit(this.onEnd, false, err); | ||
} | ||
}); | ||
if (this._socket) { | ||
this._socket.onopen = null; | ||
this._socket.onmessage = null; | ||
this._socket.onerror = null; | ||
this._socket.onclose = null; | ||
this._socket = null; | ||
} | ||
}; | ||
} | ||
disconnect() { | ||
var _a, _b; | ||
(_a = this._logger) === null || _a === void 0 ? void 0 : _a.trace('WebSocketConnection disconnect'); | ||
(_b = this._socket) === null || _b === void 0 ? void 0 : _b.close(); | ||
} | ||
} | ||
exports.WebSocketConnection = WebSocketConnection; | ||
//# sourceMappingURL=WebSocketConnection.js.map |
{ | ||
"name": "@d-fischer/connection", | ||
"version": "7.0.1", | ||
"version": "8.0.0", | ||
"description": "Abstraction for packet-based connections.", | ||
@@ -29,6 +29,6 @@ "keywords": [], | ||
"@d-fischer/logger": "^4.2.0", | ||
"@d-fischer/shared-utils": "^3.3.0", | ||
"@d-fischer/shared-utils": "^3.5.0", | ||
"@d-fischer/typed-event-emitter": "^3.3.0", | ||
"@types/node": "^16.7.10", | ||
"@types/ws": "^8.5.3", | ||
"@types/ws": "^8.5.4", | ||
"tslib": "^2.4.1", | ||
@@ -38,9 +38,9 @@ "ws": "^8.11.0" | ||
"devDependencies": { | ||
"@d-fischer/eslint-config": "^6.1.4", | ||
"eslint": "^8.27.0", | ||
"@d-fischer/eslint-config": "^6.1.8", | ||
"eslint": "^8.31.0", | ||
"husky": "^4.3.6", | ||
"lint-staged": "^13.0.3", | ||
"prettier": "^2.7.1", | ||
"tsukuru": "^0.7.4", | ||
"typescript": "~4.6.4" | ||
"lint-staged": "^13.1.0", | ||
"prettier": "^2.8.1", | ||
"tsukuru": "^0.8.0-pre.11", | ||
"typescript": "~4.9.4" | ||
}, | ||
@@ -47,0 +47,0 @@ "files": [ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
67015
-6.87%882
-8.22%Updated