diff --git a/README.md b/README.md index f3f3321..cbbb50e 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,10 @@ SignalR client library built on top of `@aspnet/signalr-client`. This gives you * Connection state notifications * Sending extra connection details easily and keeps the current connection state * Subscriptions are handled through `RxJS` streams. - * Reconnection strategies (***in development***) + * Reconnection strategies: + * Random + * BackOff + * Custom * Auto re-subscriptions after getting disconnected and re-connected (***in development***) * Contains minimal dependencies (`SignalR` and `RxJS` only) * `No constraints` with any frameworks. diff --git a/package-lock.json b/package-lock.json index 2f1309b..e1cb756 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@ssv/signalr-client", - "version": "0.3.0", + "version": "0.3.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -224,7 +224,7 @@ "chalk": "2.3.0", "cross-spawn-promise": "0.10.1", "rollup": "0.50.1", - "simple-git": "1.82.0", + "simple-git": "1.84.0", "typescript": "2.6.2" } }, @@ -240,12 +240,6 @@ "integrity": "sha512-hQbL8aBM/g5S++sM1gb4yC73Dg+FK3uYE+Ioht1RPy629+LV/RmH6q+e+jbQEwKJdWAP/YE4s67CPO+ElkMivg==", "dev": true }, - "@types/lodash": { - "version": "4.14.86", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.86.tgz", - "integrity": "sha512-DIiC7xZkI+iqwb6A28+JDfrioxcFRHAUXl+AEZ9lULQppiArWRfex4ugVUAJKZHxcgqZJ1w2de2DTahVyrEp4Q==", - "dev": true - }, "@types/stylelint": { "version": "7.10.0", "resolved": "https://registry.npmjs.org/@types/stylelint/-/stylelint-7.10.0.tgz", @@ -258,6 +252,16 @@ "integrity": "sha1-kfjiWAqAgwSfeDEcBZqlfWlJ32s=", "dev": true }, + "JSONStream": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-0.8.4.tgz", + "integrity": "sha1-kWV9/m/4V0gwZhMrRhi2Lo9Ih70=", + "dev": true, + "requires": { + "jsonparse": "0.0.5", + "through": "2.3.8" + } + }, "abab": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/abab/-/abab-1.0.4.tgz", @@ -286,9 +290,9 @@ "dev": true }, "ajv": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.0.tgz", - "integrity": "sha1-6yhAdG6dxIvV4GOjbj/UAMXqtak=", + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.1.tgz", + "integrity": "sha1-s4u4h22ehr7plJVqBOch6IskjrI=", "dev": true, "requires": { "co": "4.6.0", @@ -475,9 +479,9 @@ "dev": true }, "array-slice": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.0.0.tgz", - "integrity": "sha1-5zA08A3MH0CHYAj9IP6ud71LfC8=", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", + "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", "dev": true }, "array-union": { @@ -556,7 +560,7 @@ "dev": true, "requires": { "browserslist": "1.7.7", - "caniuse-db": "1.0.30000772", + "caniuse-db": "1.0.30000778", "normalize-range": "0.1.2", "num2fraction": "1.2.2", "postcss": "5.2.18", @@ -603,7 +607,7 @@ "dev": true, "requires": { "chalk": "1.1.3", - "js-base64": "2.3.2", + "js-base64": "2.4.0", "source-map": "0.5.7", "supports-color": "3.2.3" } @@ -997,7 +1001,7 @@ "integrity": "sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=", "dev": true, "requires": { - "caniuse-db": "1.0.30000772", + "caniuse-db": "1.0.30000778", "electron-to-chromium": "1.3.27" } }, @@ -1049,9 +1053,9 @@ } }, "caniuse-db": { - "version": "1.0.30000772", - "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000772.tgz", - "integrity": "sha1-UarokXaChureSj2DGep21qAbUSs=", + "version": "1.0.30000778", + "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000778.tgz", + "integrity": "sha1-Fnxg6VQqKqYFN8RG+ziB2FOjByo=", "dev": true }, "caseless": { @@ -1300,7 +1304,7 @@ "dev": true, "requires": { "chalk": "1.1.3", - "js-base64": "2.3.2", + "js-base64": "2.4.0", "source-map": "0.5.7", "supports-color": "3.2.3" }, @@ -1623,8 +1627,8 @@ "integrity": "sha1-HxXOa4RPfKQUlcgZDAgzwwuLFpM=", "dev": true, "requires": { - "is-text-path": "1.0.1", "JSONStream": "1.3.1", + "is-text-path": "1.0.1", "lodash": "4.17.4", "meow": "3.7.0", "split2": "2.2.0", @@ -1632,12 +1636,6 @@ "trim-off-newlines": "1.0.1" }, "dependencies": { - "jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", - "dev": true - }, "JSONStream": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.1.tgz", @@ -1648,6 +1646,12 @@ "through": "2.3.8" } }, + "jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", + "dev": true + }, "split2": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/split2/-/split2-2.2.0.tgz", @@ -2062,7 +2066,7 @@ "dev": true, "requires": { "browserslist": "1.7.7", - "caniuse-db": "1.0.30000772", + "caniuse-db": "1.0.30000778", "css-rule-stream": "1.1.0", "duplexer2": "0.0.2", "jsonfilter": "1.1.2", @@ -2115,7 +2119,7 @@ "dev": true, "requires": { "chalk": "1.1.3", - "js-base64": "2.3.2", + "js-base64": "2.4.0", "source-map": "0.5.7", "supports-color": "3.2.3" }, @@ -2290,16 +2294,16 @@ "dev": true, "requires": { "bluebird": "3.5.1", - "commander": "2.12.1", + "commander": "2.12.2", "lru-cache": "3.2.0", "semver": "5.4.1", "sigmund": "1.0.1" }, "dependencies": { "commander": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.12.1.tgz", - "integrity": "sha512-PCNLExLlI5HiPdaJs4pMXwOTHkSCpNQ1QJH9ykZLKtKEyKu3p9HgmH5l97vM8c0IUz6d54l+xEu2GG9yuYrFzA==", + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.12.2.tgz", + "integrity": "sha512-BFnaq5ZOGcDN7FlrtBT4xxkgIToalIIxwjxLWVJ8bGTpe1LroqMiqQXdA7ygc7CRvaYS+9zfPGFnJqFSayx+AA==", "dev": true }, "lru-cache": { @@ -3224,7 +3228,7 @@ "chalk": "1.1.3", "deprecated": "0.0.1", "gulp-util": "3.0.8", - "interpret": "1.0.4", + "interpret": "1.1.0", "liftoff": "2.3.0", "minimist": "1.2.0", "orchestrator": "0.3.8", @@ -3555,7 +3559,7 @@ "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", "dev": true, "requires": { - "ajv": "5.5.0", + "ajv": "5.5.1", "har-schema": "2.0.0" } }, @@ -3778,9 +3782,9 @@ "dev": true }, "interpret": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.0.4.tgz", - "integrity": "sha1-ggzdWIuGj/sZGoCVBtbJyPISsbA=", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", + "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=", "dev": true }, "invariant": { @@ -3940,13 +3944,13 @@ "integrity": "sha1-ZHdYK4IU1gI0YJRWcAO+ip6sBNw=", "dev": true, "requires": { - "is-path-inside": "1.0.0" + "is-path-inside": "1.0.1" } }, "is-path-inside": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.0.tgz", - "integrity": "sha1-/AbloWg/vaE95mev9xe7wQpI838=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", "dev": true, "requires": { "path-is-inside": "1.0.2" @@ -4743,9 +4747,9 @@ } }, "js-base64": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.3.2.tgz", - "integrity": "sha512-Y2/+DnfJJXT1/FCwUebUhLWb3QihxiSC42+ctHLGogmW2jPY6LCapMdFZXRvVP2z6qyKW7s6qncE/9gSqZiArw==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.4.0.tgz", + "integrity": "sha512-Wehd+7Pf9tFvGb+ydPm9TjYjV8X1YHOVyG8QyELZxEMqOhemVwGRmoG8iQ/soqI3n8v4xn59zaLxiCJiaaRzKA==", "dev": true }, "js-tokens": { @@ -4903,16 +4907,6 @@ "integrity": "sha1-MwVCrT8KZUZlt3jz6y2an6UHrGQ=", "dev": true }, - "JSONStream": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-0.8.4.tgz", - "integrity": "sha1-kWV9/m/4V0gwZhMrRhi2Lo9Ih70=", - "dev": true, - "requires": { - "jsonparse": "0.0.5", - "through": "2.3.8" - } - }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -5558,7 +5552,7 @@ "dev": true, "requires": { "array-each": "1.0.1", - "array-slice": "1.0.0", + "array-slice": "1.1.0", "for-own": "1.0.0", "isobject": "3.0.1" }, @@ -6058,7 +6052,7 @@ "dev": true, "requires": { "chalk": "1.1.3", - "js-base64": "2.3.2", + "js-base64": "2.4.0", "source-map": "0.5.7", "supports-color": "3.2.3" } @@ -6130,7 +6124,7 @@ "dev": true, "requires": { "chalk": "1.1.3", - "js-base64": "2.3.2", + "js-base64": "2.4.0", "source-map": "0.5.7", "supports-color": "3.2.3" }, @@ -6236,7 +6230,7 @@ "dev": true, "requires": { "chalk": "1.1.3", - "js-base64": "2.3.2", + "js-base64": "2.4.0", "source-map": "0.5.7", "supports-color": "3.2.3" } @@ -6653,11 +6647,11 @@ } }, "rxjs": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.2.tgz", - "integrity": "sha512-oRYoIKWBU3Ic37fLA5VJu31VqQO4bWubRntcHSJ+cwaDQBwdnZ9x4zmhJfm/nFQ2E82/I4loSioHnACamrKGgA==", + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.3.tgz", + "integrity": "sha512-VWockSz7xmDveeZ7wv8RvdipGGZ1NmL/m4jnpvN9BH4x1fW/TPoD23yXh+qDkbWSlajXVVfLIbGmyxa94Ls84w==", "requires": { - "symbol-observable": "1.0.4" + "symbol-observable": "1.1.0" } }, "safe-buffer": { @@ -6762,9 +6756,9 @@ "dev": true }, "simple-git": { - "version": "1.82.0", - "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-1.82.0.tgz", - "integrity": "sha1-X9Dv6cTueOXZQvJ2rEkrMuBBoBo=", + "version": "1.84.0", + "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-1.84.0.tgz", + "integrity": "sha1-koOy1NSvGowszyWJKy9h4K1ejfw=", "dev": true, "requires": { "debug": "3.1.0" @@ -6949,14 +6943,6 @@ "integrity": "sha1-pB6tGm1ggc63n2WwYZAbbY89HQ8=", "dev": true }, - "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", - "requires": { - "safe-buffer": "5.1.1" - } - }, "string-length": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/string-length/-/string-length-2.0.0.tgz", @@ -7017,6 +7003,14 @@ } } }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "requires": { + "safe-buffer": "5.1.1" + } + }, "stringstream": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", @@ -7211,7 +7205,7 @@ "dev": true, "requires": { "chalk": "1.1.3", - "js-base64": "2.3.2", + "js-base64": "2.4.0", "source-map": "0.5.7", "supports-color": "3.2.3" }, @@ -7325,7 +7319,7 @@ "dev": true, "requires": { "chalk": "1.1.3", - "js-base64": "2.3.2", + "js-base64": "2.4.0", "source-map": "0.5.7", "supports-color": "3.2.3" }, @@ -7430,7 +7424,7 @@ "dev": true, "requires": { "chalk": "1.1.3", - "js-base64": "2.3.2", + "js-base64": "2.4.0", "source-map": "0.5.7", "supports-color": "3.2.3" } @@ -7524,7 +7518,7 @@ "dev": true, "requires": { "chalk": "1.1.3", - "js-base64": "2.3.2", + "js-base64": "2.4.0", "source-map": "0.5.7", "supports-color": "3.2.3" } @@ -7562,9 +7556,9 @@ "dev": true }, "symbol-observable": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.4.tgz", - "integrity": "sha1-Kb9hXUqnEhvdiYsi1LP5vE4qoD0=" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.1.0.tgz", + "integrity": "sha512-dQoid9tqQ+uotGhuTKEY11X4xhyYePVnqGSoSm3OGKh2E8LZ6RPULp1uXTctk33IeERlrRJYoVSBglsL05F5Uw==" }, "symbol-tree": { "version": "3.2.2", @@ -7595,7 +7589,7 @@ "integrity": "sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA==", "dev": true, "requires": { - "ajv": "5.5.0", + "ajv": "5.5.1", "ajv-keywords": "2.1.1", "chalk": "2.3.0", "lodash": "4.17.4", @@ -7810,28 +7804,28 @@ "babel-code-frame": "6.26.0", "builtin-modules": "1.1.1", "chalk": "2.3.0", - "commander": "2.12.1", + "commander": "2.12.2", "diff": "3.4.0", "glob": "7.1.2", "minimatch": "3.0.4", "resolve": "1.5.0", "semver": "5.4.1", "tslib": "1.8.0", - "tsutils": "2.12.2" + "tsutils": "2.13.0" }, "dependencies": { "commander": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.12.1.tgz", - "integrity": "sha512-PCNLExLlI5HiPdaJs4pMXwOTHkSCpNQ1QJH9ykZLKtKEyKu3p9HgmH5l97vM8c0IUz6d54l+xEu2GG9yuYrFzA==", + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.12.2.tgz", + "integrity": "sha512-BFnaq5ZOGcDN7FlrtBT4xxkgIToalIIxwjxLWVJ8bGTpe1LroqMiqQXdA7ygc7CRvaYS+9zfPGFnJqFSayx+AA==", "dev": true } } }, "tsutils": { - "version": "2.12.2", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.12.2.tgz", - "integrity": "sha1-rVikhl0X7D3bZjG2ylO+FKVlb/M=", + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.13.0.tgz", + "integrity": "sha512-FuWzNJbMsp3gcZMbI3b5DomhW4Ia41vMxjN63nKWI0t7f+I3UmHfRl0TrXJTwI2LUduDG+eR1Mksp3pvtlyCFQ==", "dev": true, "requires": { "tslib": "1.8.0" diff --git a/package.json b/package.json index 700e327..3463295 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,6 @@ "devDependencies": { "@ssv/tools": "^0.6.12", "@types/jest": "^21.1.8", - "@types/lodash": "^4.14.86", "del": "^3.0.0", "gulp": "^3.9.1", "gulp-bump": "^2.8.0", @@ -50,7 +49,6 @@ "gulp-util": "^3.0.8", "jest": "^21.2.1", "jest-environment-node-debug": "^2.0.0", - "lodash": "^4.17.4", "require-dir": "^0.3.2", "run-sequence": "^2.2.0", "ts-jest": "^21.2.4", diff --git a/src/hub-connection.model.ts b/src/hub-connection.model.ts index 9b283d0..4ec0f92 100644 --- a/src/hub-connection.model.ts +++ b/src/hub-connection.model.ts @@ -22,5 +22,23 @@ export interface HubConnectionOptions { } export interface ConnectionOptions extends IHubConnectionOptions { + retry?: ReconnectionStrategyOptions; +} + +export interface ReconnectionStrategyOptions { + maximumAttempts?: number; + customStrategy?: (retryOptions: ReconnectionStrategyOptions, retryCount: number) => number; + randomStrategy?: RandomStrategyOptions; + backOffStrategy?: BackOffStrategyOptions; +} -} \ No newline at end of file +export interface RandomStrategyOptions { + min: number; + max: number; + intervalMs: number; +} + +export interface BackOffStrategyOptions { + delayRetriesMs: number; + maxDelayRetriesMs: number; +} diff --git a/src/hub-connection.ts b/src/hub-connection.ts index 86a00ed..b520535 100644 --- a/src/hub-connection.ts +++ b/src/hub-connection.ts @@ -1,11 +1,13 @@ -import { tap, map, filter, switchMap, skipUntil, take, delay } from "rxjs/operators"; +import { tap, map, filter, switchMap, skipUntil, take, delay, first, retryWhen, scan, delayWhen, defaultIfEmpty } from "rxjs/operators"; import { HubConnection as SignalRHubConnection } from "@aspnet/signalr-client"; import { fromPromise } from "rxjs/observable/fromPromise"; +import { timer } from "rxjs/observable/timer"; import { BehaviorSubject } from "rxjs/BehaviorSubject"; import { Observable } from "rxjs/Observable"; import { Observer } from "rxjs/Observer"; -import { ConnectionState, ConnectionStatus, HubConnectionOptions } from "./hub-connection.model"; +import { ConnectionState, ConnectionStatus, HubConnectionOptions, ReconnectionStrategyOptions } from "./hub-connection.model"; +import { getDelay } from "./reconnection-strategy"; import { buildQueryString } from "./utils/query-string"; import { Dictionary } from "./utils/dictionary"; import { emptyNext } from "./utils/rxjs"; @@ -20,12 +22,14 @@ export class HubConnection { private source: string; private hubConnection: SignalRHubConnection; + private retry: ReconnectionStrategyOptions; private hubConnectionOptions$: BehaviorSubject; private _connectionState$ = new BehaviorSubject(disconnectedState); constructor(connectionOption: HubConnectionOptions) { this.source = `[${connectionOption.key}] HubConnection ::`; + this.retry = connectionOption.options && connectionOption.options.retry ? connectionOption.options.retry : {}; this.hubConnectionOptions$ = new BehaviorSubject(connectionOption); const reconnection$ = this.hubConnectionOptions$.pipe( @@ -59,7 +63,7 @@ export class HubConnection { } }), skipUntil(this._connectionState$.pipe(filter(x => x.status === ConnectionStatus.ready))), - take(1) + first() )), switchMap(() => this.openConnection()) ); @@ -110,7 +114,7 @@ export class HubConnection { return emptyNext().pipe( switchMap(() => this.connectionState$.pipe( skipUntil(this.connectionState$.pipe(filter(x => x.status === ConnectionStatus.connected))), - take(1) + first() )), tap(() => console.log(`${this.source} stream - start`)), switchMap(() => stream$) // todo: retry after reconnection @@ -137,7 +141,19 @@ export class HubConnection { } private openConnection() { - return fromPromise(this.hubConnection.start()).pipe( + return emptyNext().pipe( + switchMap(() => fromPromise(this.hubConnection.start())), + retryWhen((errors: Observable) => errors.pipe( + scan((errorCount: number) => ++errorCount, 0), + this.retry.maximumAttempts ? take(this.retry.maximumAttempts) : defaultIfEmpty(), + delayWhen((retryCount: number) => { + const delayRetries = getDelay(this.retry, retryCount); + // tslint:disable-next-line:no-console + console.debug(`${this.source} connect :: retrying`, { retryCount, maximumAttempts: this.retry.maximumAttempts, delayRetries }); + this.hubConnectionOptions$.next(this.hubConnectionOptions$.value); + return timer(delayRetries); + }) + )), tap(() => this._connectionState$.next(connectedState)), tap(() => { this.hubConnection.onclose(err => { @@ -149,8 +165,7 @@ export class HubConnection { this._connectionState$.next(disconnectedState); } }); - }), - take(1) - ); // todo: retry + }) + ); } } \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 7b9dacb..fb5f108 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,14 @@ import { HubConnection } from "./hub-connection"; import { HubConnectionFactory } from "./hub-connection.factory"; -import { ConnectionStatus, ConnectionState, HubConnectionOptions, ConnectionOptions } from "./hub-connection.model"; +import { + ConnectionStatus, + ConnectionState, + HubConnectionOptions, + ConnectionOptions, + ReconnectionStrategyOptions, + RandomStrategyOptions, + BackOffStrategyOptions +} from "./hub-connection.model"; export { HubConnection, @@ -9,5 +17,8 @@ export { ConnectionStatus, ConnectionState, ConnectionOptions, - HubConnectionOptions + HubConnectionOptions, + ReconnectionStrategyOptions, + RandomStrategyOptions, + BackOffStrategyOptions }; \ No newline at end of file diff --git a/src/reconnection-strategy.ts b/src/reconnection-strategy.ts new file mode 100644 index 0000000..aaffb17 --- /dev/null +++ b/src/reconnection-strategy.ts @@ -0,0 +1,27 @@ +import { ReconnectionStrategyOptions, BackOffStrategyOptions, RandomStrategyOptions } from "./hub-connection.model"; +import { random } from "./utils/math"; + +export function getDelay(retryOptions: ReconnectionStrategyOptions, retryCount: number): number { + if (retryOptions.customStrategy) { + return retryOptions.customStrategy(retryOptions, retryCount); + } + if (retryOptions.backOffStrategy) { + return backOffStrategyDelay(retryOptions.backOffStrategy, retryCount); + } + if (retryOptions.randomStrategy) { + return randomStrategyDelay(retryOptions.randomStrategy); + } + return defaultStrategy(retryCount); +} + +function randomStrategyDelay(randomStrategy: RandomStrategyOptions) { + return random(randomStrategy.min, randomStrategy.max) * randomStrategy.intervalMs; +} + +function backOffStrategyDelay(backOffStrategy: BackOffStrategyOptions, retryCount: number) { + return Math.min(retryCount * backOffStrategy.delayRetriesMs, backOffStrategy.maxDelayRetriesMs); +} + +function defaultStrategy(retryCount: number) { + return backOffStrategyDelay({ delayRetriesMs: 1000, maxDelayRetriesMs: 15000 }, retryCount); +} \ No newline at end of file diff --git a/src/utils/math.ts b/src/utils/math.ts new file mode 100644 index 0000000..a92af0d --- /dev/null +++ b/src/utils/math.ts @@ -0,0 +1,3 @@ +export function random(min: number, max: number) { + return Math.floor(Math.random() * (max - min + 1) + min); +} \ No newline at end of file diff --git a/src/utils/rxjs.ts b/src/utils/rxjs.ts index 1e41ba1..8107260 100644 --- a/src/utils/rxjs.ts +++ b/src/utils/rxjs.ts @@ -1,7 +1,7 @@ import { Observable } from "rxjs/Observable"; import { of } from "rxjs/observable/of"; -import { take } from "rxjs/operators"; +import { first } from "rxjs/operators"; export function emptyNext(): Observable { - return of(undefined).pipe(take(1)); -} + return of(undefined).pipe(first()); +} \ No newline at end of file