123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342 |
- "use strict";
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
- return new (P || (P = Promise))(function (resolve, reject) {
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
- step((generator = generator.apply(thisArg, _arguments || [])).next());
- });
- };
- var __importDefault = (this && this.__importDefault) || function (mod) {
- return (mod && mod.__esModule) ? mod : { "default": mod };
- };
- Object.defineProperty(exports, "__esModule", { value: true });
- exports.Connection = exports.INTENTIONAL_DISCONNECT_CODE = void 0;
- const events_1 = require("events");
- const omitBy_1 = __importDefault(require("lodash/omitBy"));
- const ws_1 = __importDefault(require("ws"));
- const errors_1 = require("../errors");
- const ConnectionManager_1 = __importDefault(require("./ConnectionManager"));
- const ExponentialBackoff_1 = __importDefault(require("./ExponentialBackoff"));
- const RequestManager_1 = __importDefault(require("./RequestManager"));
- const SECONDS_PER_MINUTE = 60;
- const TIMEOUT = 20;
- const CONNECTION_TIMEOUT = 5;
- exports.INTENTIONAL_DISCONNECT_CODE = 4000;
- function getAgent(url, config) {
- if (config.proxy == null) {
- return undefined;
- }
- const parsedURL = new URL(url);
- const parsedProxyURL = new URL(config.proxy);
- const proxyOptions = (0, omitBy_1.default)({
- secureEndpoint: parsedURL.protocol === 'wss:',
- secureProxy: parsedProxyURL.protocol === 'https:',
- auth: config.proxyAuthorization,
- ca: config.trustedCertificates,
- key: config.key,
- passphrase: config.passphrase,
- cert: config.certificate,
- href: parsedProxyURL.href,
- origin: parsedProxyURL.origin,
- protocol: parsedProxyURL.protocol,
- username: parsedProxyURL.username,
- password: parsedProxyURL.password,
- host: parsedProxyURL.host,
- hostname: parsedProxyURL.hostname,
- port: parsedProxyURL.port,
- pathname: parsedProxyURL.pathname,
- search: parsedProxyURL.search,
- hash: parsedProxyURL.hash,
- }, (value) => value == null);
- let HttpsProxyAgent;
- try {
- HttpsProxyAgent = require('https-proxy-agent');
- }
- catch (_error) {
- throw new Error('"proxy" option is not supported in the browser');
- }
- return new HttpsProxyAgent(proxyOptions);
- }
- function createWebSocket(url, config) {
- const options = {};
- options.agent = getAgent(url, config);
- if (config.headers) {
- options.headers = config.headers;
- }
- if (config.authorization != null) {
- const base64 = Buffer.from(config.authorization).toString('base64');
- options.headers = Object.assign(Object.assign({}, options.headers), { Authorization: `Basic ${base64}` });
- }
- const optionsOverrides = (0, omitBy_1.default)({
- ca: config.trustedCertificates,
- key: config.key,
- passphrase: config.passphrase,
- cert: config.certificate,
- }, (value) => value == null);
- const websocketOptions = Object.assign(Object.assign({}, options), optionsOverrides);
- const websocket = new ws_1.default(url, websocketOptions);
- if (typeof websocket.setMaxListeners === 'function') {
- websocket.setMaxListeners(Infinity);
- }
- return websocket;
- }
- function websocketSendAsync(ws, message) {
- return __awaiter(this, void 0, void 0, function* () {
- return new Promise((resolve, reject) => {
- ws.send(message, (error) => {
- if (error) {
- reject(new errors_1.DisconnectedError(error.message, error));
- }
- else {
- resolve();
- }
- });
- });
- });
- }
- class Connection extends events_1.EventEmitter {
- constructor(url, options = {}) {
- super();
- this.ws = null;
- this.reconnectTimeoutID = null;
- this.heartbeatIntervalID = null;
- this.retryConnectionBackoff = new ExponentialBackoff_1.default({
- min: 100,
- max: SECONDS_PER_MINUTE * 1000,
- });
- this.requestManager = new RequestManager_1.default();
- this.connectionManager = new ConnectionManager_1.default();
- this.trace = () => { };
- this.setMaxListeners(Infinity);
- this.url = url;
- this.config = Object.assign({ timeout: TIMEOUT * 1000, connectionTimeout: CONNECTION_TIMEOUT * 1000 }, options);
- if (typeof options.trace === 'function') {
- this.trace = options.trace;
- }
- else if (options.trace) {
- this.trace = console.log;
- }
- }
- get state() {
- return this.ws ? this.ws.readyState : ws_1.default.CLOSED;
- }
- get shouldBeConnected() {
- return this.ws !== null;
- }
- isConnected() {
- return this.state === ws_1.default.OPEN;
- }
- connect() {
- return __awaiter(this, void 0, void 0, function* () {
- if (this.isConnected()) {
- return Promise.resolve();
- }
- if (this.state === ws_1.default.CONNECTING) {
- return this.connectionManager.awaitConnection();
- }
- if (!this.url) {
- return Promise.reject(new errors_1.ConnectionError('Cannot connect because no server was specified'));
- }
- if (this.ws != null) {
- return Promise.reject(new errors_1.XrplError('Websocket connection never cleaned up.', {
- state: this.state,
- }));
- }
- const connectionTimeoutID = setTimeout(() => {
- this.onConnectionFailed(new errors_1.ConnectionError(`Error: connect() timed out after ${this.config.connectionTimeout} ms. If your internet connection is working, the ` +
- `rippled server may be blocked or inaccessible. You can also try setting the 'connectionTimeout' option in the Client constructor.`));
- }, this.config.connectionTimeout);
- this.ws = createWebSocket(this.url, this.config);
- if (this.ws == null) {
- throw new errors_1.XrplError('Connect: created null websocket');
- }
- this.ws.on('error', (error) => this.onConnectionFailed(error));
- this.ws.on('error', () => clearTimeout(connectionTimeoutID));
- this.ws.on('close', (reason) => this.onConnectionFailed(reason));
- this.ws.on('close', () => clearTimeout(connectionTimeoutID));
- this.ws.once('open', () => {
- void this.onceOpen(connectionTimeoutID);
- });
- return this.connectionManager.awaitConnection();
- });
- }
- disconnect() {
- return __awaiter(this, void 0, void 0, function* () {
- this.clearHeartbeatInterval();
- if (this.reconnectTimeoutID !== null) {
- clearTimeout(this.reconnectTimeoutID);
- this.reconnectTimeoutID = null;
- }
- if (this.state === ws_1.default.CLOSED) {
- return Promise.resolve(undefined);
- }
- if (this.ws == null) {
- return Promise.resolve(undefined);
- }
- return new Promise((resolve) => {
- if (this.ws == null) {
- resolve(undefined);
- }
- if (this.ws != null) {
- this.ws.once('close', (code) => resolve(code));
- }
- if (this.ws != null && this.state !== ws_1.default.CLOSING) {
- this.ws.close(exports.INTENTIONAL_DISCONNECT_CODE);
- }
- });
- });
- }
- reconnect() {
- return __awaiter(this, void 0, void 0, function* () {
- this.emit('reconnect');
- yield this.disconnect();
- yield this.connect();
- });
- }
- request(request, timeout) {
- return __awaiter(this, void 0, void 0, function* () {
- if (!this.shouldBeConnected || this.ws == null) {
- throw new errors_1.NotConnectedError(JSON.stringify(request), request);
- }
- const [id, message, responsePromise] = this.requestManager.createRequest(request, timeout !== null && timeout !== void 0 ? timeout : this.config.timeout);
- this.trace('send', message);
- websocketSendAsync(this.ws, message).catch((error) => {
- this.requestManager.reject(id, error);
- });
- return responsePromise;
- });
- }
- getUrl() {
- var _a;
- return (_a = this.url) !== null && _a !== void 0 ? _a : '';
- }
- onMessage(message) {
- this.trace('receive', message);
- let data;
- try {
- data = JSON.parse(message);
- }
- catch (error) {
- if (error instanceof Error) {
- this.emit('error', 'badMessage', error.message, message);
- }
- return;
- }
- if (data.type == null && data.error) {
- this.emit('error', data.error, data.error_message, data);
- return;
- }
- if (data.type) {
- this.emit(data.type, data);
- }
- if (data.type === 'response') {
- try {
- this.requestManager.handleResponse(data);
- }
- catch (error) {
- if (error instanceof Error) {
- this.emit('error', 'badMessage', error.message, message);
- }
- else {
- this.emit('error', 'badMessage', error, error);
- }
- }
- }
- }
- onceOpen(connectionTimeoutID) {
- return __awaiter(this, void 0, void 0, function* () {
- if (this.ws == null) {
- throw new errors_1.XrplError('onceOpen: ws is null');
- }
- this.ws.removeAllListeners();
- clearTimeout(connectionTimeoutID);
- this.ws.on('message', (message) => this.onMessage(message));
- this.ws.on('error', (error) => this.emit('error', 'websocket', error.message, error));
- this.ws.once('close', (code, reason) => {
- if (this.ws == null) {
- throw new errors_1.XrplError('onceClose: ws is null');
- }
- this.clearHeartbeatInterval();
- this.requestManager.rejectAll(new errors_1.DisconnectedError(`websocket was closed, ${new TextDecoder('utf-8').decode(reason)}`));
- this.ws.removeAllListeners();
- this.ws = null;
- if (code === undefined) {
- const internalErrorCode = 1011;
- this.emit('disconnected', internalErrorCode);
- }
- else {
- this.emit('disconnected', code);
- }
- if (code !== exports.INTENTIONAL_DISCONNECT_CODE && code !== undefined) {
- this.intentionalDisconnect();
- }
- });
- try {
- this.retryConnectionBackoff.reset();
- this.startHeartbeatInterval();
- this.connectionManager.resolveAllAwaiting();
- this.emit('connected');
- }
- catch (error) {
- if (error instanceof Error) {
- this.connectionManager.rejectAllAwaiting(error);
- yield this.disconnect().catch(() => { });
- }
- }
- });
- }
- intentionalDisconnect() {
- const retryTimeout = this.retryConnectionBackoff.duration();
- this.trace('reconnect', `Retrying connection in ${retryTimeout}ms.`);
- this.emit('reconnecting', this.retryConnectionBackoff.attempts);
- this.reconnectTimeoutID = setTimeout(() => {
- this.reconnect().catch((error) => {
- this.emit('error', 'reconnect', error.message, error);
- });
- }, retryTimeout);
- }
- clearHeartbeatInterval() {
- if (this.heartbeatIntervalID) {
- clearInterval(this.heartbeatIntervalID);
- }
- }
- startHeartbeatInterval() {
- this.clearHeartbeatInterval();
- this.heartbeatIntervalID = setInterval(() => {
- void this.heartbeat();
- }, this.config.timeout);
- }
- heartbeat() {
- return __awaiter(this, void 0, void 0, function* () {
- this.request({ command: 'ping' }).catch(() => __awaiter(this, void 0, void 0, function* () {
- return this.reconnect().catch((error) => {
- this.emit('error', 'reconnect', error.message, error);
- });
- }));
- });
- }
- onConnectionFailed(errorOrCode) {
- if (this.ws) {
- this.ws.removeAllListeners();
- this.ws.on('error', () => {
- });
- this.ws.close();
- this.ws = null;
- }
- if (typeof errorOrCode === 'number') {
- this.connectionManager.rejectAllAwaiting(new errors_1.NotConnectedError(`Connection failed with code ${errorOrCode}.`, {
- code: errorOrCode,
- }));
- }
- else if (errorOrCode === null || errorOrCode === void 0 ? void 0 : errorOrCode.message) {
- this.connectionManager.rejectAllAwaiting(new errors_1.NotConnectedError(errorOrCode.message, errorOrCode));
- }
- else {
- this.connectionManager.rejectAllAwaiting(new errors_1.NotConnectedError('Connection failed.'));
- }
- }
- }
- exports.Connection = Connection;
- //# sourceMappingURL=connection.js.map
|