@truffle/dashboard-message-bus-client
Version:
Client library for accessing the truffle dashboard's message bus
228 lines • 11.7 kB
JavaScript
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
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.DashboardMessageBusConnection = void 0;
const isomorphic_ws_1 = __importDefault(require("isomorphic-ws"));
// must polyfill AbortController to use axios >=0.20.0, <=0.27.2 on node <= v14.x
require("../polyfill");
const axios_1 = __importDefault(require("axios"));
const dashboard_message_bus_common_1 = require("@truffle/dashboard-message-bus-common");
const debug_1 = __importDefault(require("debug"));
const errors_1 = require("../errors");
const tiny_typed_emitter_1 = require("tiny-typed-emitter");
const promise_tracker_1 = require("@truffle/promise-tracker");
const delay_1 = __importDefault(require("delay"));
const debug = (0, debug_1.default)("dashboard-message-bus-client:connection");
const debugMessage = (0, debug_1.default)("dashboard-message-bus-client:message");
class DashboardMessageBusConnection extends tiny_typed_emitter_1.TypedEmitter {
constructor({ host, port, publishPort, subscribePort, connectionType: socketType }) {
super();
this._host = host;
this._port = port;
this._publishPort = publishPort;
this._subscribePort = subscribePort;
this._connectionType = socketType;
}
get isConnected() {
return this._socket && this._socket.readyState === isomorphic_ws_1.default.OPEN;
}
get isConnecting() {
return this._socket && this._socket.readyState === isomorphic_ws_1.default.CONNECTING;
}
get isClosing() {
return this._socket && this._socket.readyState === isomorphic_ws_1.default.CLOSING;
}
connect() {
var _a;
return __awaiter(this, void 0, void 0, function* () {
if (this._socket) {
switch (this._socket.readyState) {
case isomorphic_ws_1.default.CONNECTING:
debug("connect: %s already attempting to connect (readyState switch)", this._connectionType);
yield (0, delay_1.default)(10);
return this.connect();
case isomorphic_ws_1.default.OPEN:
// we're already connected, just return
debug("connect: %s already connected", this._connectionType);
return;
case isomorphic_ws_1.default.CLOSING:
case isomorphic_ws_1.default.CLOSED:
debug("connect: %s was previously connected but has been closed", this._connectionType);
// already closed or on our way there, so we'll just recreate it in a
// moment
delete this._socket;
}
}
try {
if (this._connecting) {
debug("connect: %s already attempting to connect (_connecting flag)", this._connectionType);
yield (0, delay_1.default)(10);
return this.connect();
}
this._connecting = true;
const port = yield this._getMessageBusPort();
const url = `ws://${this._host}:${port}`;
debug("connect: %s is attempting to connect to %s", this._connectionType, url);
this._socket = new isomorphic_ws_1.default(url);
(_a = this._socket) === null || _a === void 0 ? void 0 : _a.addEventListener("message", ((event) => {
if (typeof event.data !== "string") {
event.data = event.data.toString();
}
const message = (0, dashboard_message_bus_common_1.base64ToJson)(event.data);
debugMessage("%s connection received message %o", this._connectionType, message);
this.emit("message", message);
}).bind(this));
// connecting
// we now have a socket that's in the process of opening, so return a
// promise that resolves when it opens, or fails to open
const connectPromise = this._createEventWrapperPromise((resolve, reject) => {
return {
open: () => {
var _a, _b;
debug("connect: %s connection succeeded to url %s", this._connectionType, (_a = this._socket) === null || _a === void 0 ? void 0 : _a.url);
if (this._connectionType === "subscribe") {
(_b = this._socket) === null || _b === void 0 ? void 0 : _b.send("ready");
}
resolve();
this._connecting = false;
},
error: (event) => {
var _a;
debug("connect: %s connection to url %s failed due to error %s", this._connectionType, (_a = this._socket) === null || _a === void 0 ? void 0 : _a.url, event.error);
reject(new errors_1.MessageBusConnectionError({
message: event.error.message,
cause: event.error
}));
this._connecting = false;
},
close: (event) => {
var _a;
debug("connect: %s connection to url %s closed before successfully connecting due to code %s and reason %s", this._connectionType, (_a = this._socket) === null || _a === void 0 ? void 0 : _a.url, event.code, event.reason);
reject(new errors_1.MessageBusConnectionError({
message: `Socket connection closed with code '${event.code}' and reason '${event.reason}'`
}));
this._connecting = false;
}
};
});
let timedout = false;
yield Promise.race([
connectPromise,
() => __awaiter(this, void 0, void 0, function* () {
yield (0, delay_1.default)(350);
timedout = true;
})
]);
if (timedout) {
debug("connect: %s connection to url %s timed out", this._connectionType, url);
}
}
catch (_b) {
this._connecting = false;
}
});
}
send(dataOrMessage) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
const encodedMessage = typeof dataOrMessage === "string"
? dataOrMessage
: (0, dashboard_message_bus_common_1.jsonToBase64)(dataOrMessage);
yield this.connect();
debug("send: %s connection sending %o", this._connectionType, (0, dashboard_message_bus_common_1.base64ToJson)(encodedMessage));
(_a = this._socket) === null || _a === void 0 ? void 0 : _a.send(encodedMessage);
});
}
close() {
return __awaiter(this, void 0, void 0, function* () {
if (!this._socket) {
return;
}
if (this._socket.readyState <= isomorphic_ws_1.default.CLOSING) {
const promise = this._createEventWrapperPromise((resolve, reject) => {
return {
error: (event) => {
reject(event.error);
},
close: () => {
debug("%s connection closed", this._connectionType);
resolve();
}
};
});
this._socket.close();
return promise;
}
});
}
_getMessageBusPort() {
return __awaiter(this, void 0, void 0, function* () {
if (this._connectionType === "subscribe" && this._subscribePort) {
return this._subscribePort;
}
if (this._connectionType === "publish" && this._publishPort) {
return this._publishPort;
}
// otherwise, fetch it from the server
try {
debug("_getMessageBusPort: %s connection attempting to fetch ports", this._connectionType);
const { data } = yield axios_1.default.get(`http://${this._host}:${this._port}/ports`, {
timeout: 350
});
const port = this._connectionType === "subscribe"
? data.subscribePort
: data.publishPort;
debug("_getMessageBusPort: %s connection will use port %s", this._connectionType, port);
return port;
}
catch (err) {
debug("_getMessageBusPort: failed fetching ports for %s connection due to error %s", this._connectionType, err);
throw err;
}
});
}
_createEventWrapperPromise(handlerFactory) {
return new Promise(((resolve, reject) => {
this._registerEventHandlers(handlerFactory.call(this, resolve, reject));
}).bind(this));
}
_registerEventHandlers(handlers) {
var _a;
let wrappedHandlers = {};
for (const eventType in handlers) {
wrappedHandlers[eventType] = ((...args) => {
handlers[eventType].call(this, ...args);
this._cleanUpEventHandlers(wrappedHandlers);
}).bind(this);
(_a = this._socket) === null || _a === void 0 ? void 0 : _a.addEventListener(eventType, wrappedHandlers[eventType]);
}
}
_cleanUpEventHandlers(handlers) {
var _a;
for (const eventType in handlers) {
(_a = this._socket) === null || _a === void 0 ? void 0 : _a.removeEventListener(eventType, handlers[eventType]);
}
}
}
__decorate([
promise_tracker_1.tracked
], DashboardMessageBusConnection.prototype, "send", null);
exports.DashboardMessageBusConnection = DashboardMessageBusConnection;
//# sourceMappingURL=index.js.map