UNPKG

diffusion

Version:

Diffusion JavaScript client

618 lines (617 loc) 29.5 kB
"use strict"; /** * @module Messages */ 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 __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; Object.defineProperty(exports, "__esModule", { value: true }); exports.MessagesImpl = void 0; var errors_1 = require("./../../errors/errors"); var command_service_1 = require("./../client/command-service"); var ControlGroup = require("./../control/control-group"); var registration_1 = require("./../control/registration"); var Services = require("./../services/messages-services"); var services_1 = require("./../services/services"); var session_id_1 = require("./../session/session-id"); var logger = require("./../util/logger"); var require_non_null_1 = require("./../util/require-non-null"); var response_success_1 = require("./../util/response-success"); var error_reason_1 = require("../../errors/error-reason"); var log = logger.create('Session.Messages'); /** * A request stream proxy */ var RequestStreamProxy = /** @class */ (function () { /** * Create a new RequestStreamProxy instance * * @param reqType the request data type * @param resType the response data type * @param stream the request stream */ function RequestStreamProxy(reqType, resType, stream) { this.reqType = reqType; this.resType = resType; this.stream = stream; } return RequestStreamProxy; }()); /** * A responder to message requests */ var RequestResponder = /** @class */ (function () { /** * Create a new RequestResponder instance * * @param callback the command service callback to send the response to * @param responseType the response data type */ function RequestResponder(callback, context, responseType) { this.callback = callback; this.context = context; this.responseType = responseType; } /** * Dispatch a response to a request. * * @param response the response to send * @param dataType the datatype of the response * @throws an {@link IllegalArgumentError} if the response cannot be serialised */ RequestResponder.prototype.respond = function (response) { // eslint-disable-next-line @typescript-eslint/naming-convention var DataTypes = this.context.DataTypes; if (arguments.length === 0) { throw new errors_1.IllegalArgumentError('No argument provided to the responder'); } if ((response === null || response === undefined) && !this.responseType) { throw new errors_1.IllegalArgumentError('No response type has been provided and the value provided is null. ' + 'Unable to determine the response type.'); } var dataType = this.responseType ? DataTypes.getChecked(this.responseType) : DataTypes.getByValue(response); if (dataType === undefined) { throw new errors_1.IllegalArgumentError('Unable to determine the response type.'); } var responseData = dataType.writeValueToArray(response); this.callback.respond({ responseDataType: dataType.name(), data: responseData }); }; /** * Reject a message * * @param message the message indicating the failure */ RequestResponder.prototype.reject = function (message) { this.callback.fail(error_reason_1.ErrorReason.REJECTED_REQUEST, message); }; return RequestResponder; }()); /** * Returns a session ID if parseable, else false. * * @param str the string containing the session ID * @return the session ID or `false` */ function parseSessionId(str) { try { return session_id_1.SessionId.fromString(str); } catch (err) { return false; } } /** * Implementation of the {@link Messages} feature */ var MessagesImpl = /** @class */ (function () { /** * Create a new MessagesImpl instance * * @param internal the internal session */ function MessagesImpl(internal) { var _this = this; /** * The streams registered for the request-response service */ this.requestStreams = {}; this.internal = internal; // eslint-disable-next-line @typescript-eslint/naming-convention var DataTypes = this.internal.getContext().DataTypes; var serviceLocator = internal.getServiceLocator(); this.MESSAGING_SEND = serviceLocator.obtain(Services.MESSAGING_SEND); this.MESSAGING_FILTER_SEND = serviceLocator.obtain(Services.MESSAGING_FILTER_SEND); this.MESSAGING_RECEIVER_SERVER = serviceLocator.obtain(Services.MESSAGING_RECEIVER_SERVER); this.deregisterRequestHandler = serviceLocator.obtain(services_1.MESSAGING_RECEIVER_CONTROL_DEREGISTRATION); var serviceRegistry = internal.getServiceRegistry(); var formatNoStreamForPath = function (sessionID, path) { return "Session " + sessionID + " has no registered streams for message sent to path '" + path + "'"; }; serviceRegistry.add(Services.MESSAGING_SEND, command_service_1.create(function (session, request, callback) { var proxy = _this.requestStreams[request.path]; // tslint:disable-next-line:strict-type-predicates if (proxy === undefined) { callback.fail(error_reason_1.ErrorReason.UNHANDLED_MESSAGE, formatNoStreamForPath(session.getSessionId(), request.path)); return; } var responder = new RequestResponder(callback, internal.getContext(), proxy.resType); var requestDataType = DataTypes.get(request.dataType); var requestType = proxy.reqType ? proxy.reqType : requestDataType.valueClass; if (requestDataType.canReadAs(requestType)) { var requestData = void 0; try { requestData = requestDataType.readAs(requestType, request.request); } catch (e) { log.error("Failed to convert " + requestDataType.name() + " datatype to " + requestType); proxy.stream.onError(error_reason_1.ErrorReason.INVALID_DATA); delete _this.requestStreams[request.path]; return; } try { proxy.stream.onRequest(request.path, requestData, responder); } catch (e) { log.error('Messaging request stream threw an error', e); callback.fail(error_reason_1.ErrorReason.CALLBACK_EXCEPTION, e.stack); } } else { var message = "Messaging request for path '" + request.path + "' with " + requestDataType.name() + " " + ("datatype is incompatible for stream (" + requestType.name + ")"); log.debug(message); callback.fail(error_reason_1.ErrorReason.INCOMPATIBLE_DATATYPE, message); } })); serviceRegistry.add(Services.FILTER_RESPONSE, command_service_1.create(function (session, request, callback) { callback.respond(); session.getConversationSet().respondIfPresent(request.cid, request); })); serviceRegistry.add(Services.MESSAGING_RECEIVER_CLIENT, command_service_1.create(function (session, request, callback) { session.getConversationSet().respondIfPresent(request.cid, { request: request, callback: callback.respond, fail: callback.fail }); })); } /** * @inheritdoc */ MessagesImpl.prototype.addRequestHandler = function (path, handler, keys, requestType) { var _this = this; // eslint-disable-next-line @typescript-eslint/naming-convention var DataTypes = this.internal.getContext().DataTypes; return new Promise(function (resolve, reject) { if (_this.internal.checkConnected(reject)) { try { require_non_null_1.requireNonNull(path, 'Message path'); require_non_null_1.requireNonNull(handler, 'Request handler'); } catch (e) { reject(e); return; } log.debug('Adding request handler', path); var params_1 = { definition: Services.MESSAGING_RECEIVER_CLIENT, group: ControlGroup.DEFAULT, path: path, keys: keys !== undefined ? keys : [] }; var adapter = { active: function (close, cid) { log.debug('Request handler active', path); var registration = { close: function () { return new Promise(function (resolveRegistration) { _this.deregisterRequestHandler.send(params_1, function (err, response) { if (err) { _this.internal.getConversationSet().discard(cid, err); log.debug('Error with request handler deregistration: ', err); } else { handler.onClose(); _this.internal.getConversationSet().respondIfPresent(cid, response); } resolveRegistration(); }); }); } }; resolve(registration); }, respond: function (pair) { log.debug('Request handler pair.request', path); var context = { sessionId: pair.request.sessionID, path: pair.request.path, properties: pair.request.properties }; try { var requestDatatype = requestType ? requestType : DataTypes.getChecked(pair.request.dataType); var request = requestDatatype.readValue(pair.request.content); var responder = { respond: function (response, respType) { var responseType = DataTypes.getChecked(respType !== undefined ? respType : response); pair.callback({ responseDataType: responseType.name(), data: responseType.writeValueToArray(response) }); }, reject: function (message) { pair.fail(error_reason_1.ErrorReason.REJECTED_REQUEST, message, false); } }; handler.onRequest(request, context, responder); return false; } catch (err) { log.info('An exception has occurred whilst processing the request:', err); handler.onError(err); pair.fail(error_reason_1.ErrorReason.CALLBACK_EXCEPTION, err && err.message); return false; } }, close: function () { log.debug('Request handler closed', path); handler.onClose(); } }; registration_1.registerRequestHandler(_this.internal, params_1, adapter).then(undefined, function (err) { reject(err); }); } }); }; /** * Send a request to the controller * * This is called by {@link sendRequest} when no session was specified * * @param resolve a callback to resolve the request * @param reject a callback to signal an error * @param path the path to send the request to * @param request the request to send * @param requestType an optional request {@link DataType DataType} * @param responseType an optional response {@link DataType DataType} * @throws an {@link IllegalArgumentError} if the request cannot be serialised */ MessagesImpl.prototype.sendRequestToController = function (resolve, reject, path, request, reqType, respType) { // eslint-disable-next-line @typescript-eslint/naming-convention var DataTypes = this.internal.getContext().DataTypes; try { require_non_null_1.requireNonNull(path, 'Message path'); require_non_null_1.requireNonNull(request, 'Request'); } catch (e) { reject(e); return; } var requestType; try { requestType = reqType ? DataTypes.getChecked(reqType) : DataTypes.getByValue(request); } catch (e) { reject(e); return; } if (requestType === undefined) { reject(new Error('Could not determine request type')); return; } log.debug('Sending request to server', request); this.MESSAGING_SEND.send({ path: path, dataType: requestType.name(), request: requestType.writeValueToArray(request) }, function (err, response) { var responseType; if (!response_success_1.responseSuccess(err, response)) { log.debug('Request failed', path); reject(err); } else { log.debug('Request complete', path); try { responseType = DataTypes.getChecked(respType ? respType : response.responseDataType); resolve(responseType.readValue(response.data)); } catch (e) { reject(e); } } }); }; /** * Send a request to a specific session * * This is called by {@link sendRequest} when a session was specified * * @param resolve a callback to resolve the request * @param reject a callback to signal an error * @param path the path to send the request to * @param request the request to send * @param sessionId the target recipient's session ID * @param requestType an optional request {@link DataType DataType} * @param responseType an optional response {@link DataType DataType} * @throws an {@link IllegalArgumentError} if the request cannot be serialised */ MessagesImpl.prototype.sendRequestToSession = function (resolve, reject, path, request, sessionId, reqType, respType) { // eslint-disable-next-line @typescript-eslint/naming-convention var DataTypes = this.internal.getContext().DataTypes; try { require_non_null_1.requireNonNull(sessionId, 'Session ID'); require_non_null_1.requireNonNull(path, 'Message path'); require_non_null_1.requireNonNull(request, 'Request'); } catch (e) { reject(e); return; } var requestType; try { requestType = reqType ? DataTypes.getChecked(reqType) : DataTypes.getByValue(request); } catch (e) { reject(e); return; } if (requestType === undefined) { reject(new Error('Could not determine request type')); return; } log.debug('Sending request to session', sessionId, request); this.MESSAGING_RECEIVER_SERVER.send({ sessionId: sessionId, path: path, dataType: requestType.name(), request: requestType.writeValueToArray(request) }, function (err, response) { var responseType; if (!response_success_1.responseSuccess(err, response)) { log.debug('Request failed', path); reject(err); } else { log.debug('Request complete', path); try { responseType = DataTypes.getChecked(respType ? respType : response.responseDataType); resolve(responseType.readValue(response.data)); } catch (e) { reject(e); } } }); }; /** * @inheritdoc */ MessagesImpl.prototype.sendRequest = function (path, request, // eslint-disable-next-line max-len sessionIdOrRequestType, requestTypeOrResponseType, responseType) { var _this = this; return new Promise(function (resolve, reject) { if (_this.internal.checkConnected(reject)) { try { if (sessionIdOrRequestType instanceof session_id_1.SessionId) { _this.sendRequestToSession(resolve, reject, path, request, sessionIdOrRequestType, requestTypeOrResponseType, responseType); } else if (typeof sessionIdOrRequestType === 'string') { var sessionId = parseSessionId(sessionIdOrRequestType); if (sessionId) { _this.sendRequestToSession(resolve, reject, path, request, sessionId, requestTypeOrResponseType, responseType); } else { _this.sendRequestToController(resolve, reject, path, request, sessionIdOrRequestType, requestTypeOrResponseType); } } else { _this.sendRequestToController(resolve, reject, path, request, sessionIdOrRequestType, requestTypeOrResponseType); } } catch (e) { reject(e); } } }); }; /** * @inheritdoc */ MessagesImpl.prototype.sendRequestToFilter = function (filter, path, request, callback, reqType, respType) { var _this = this; // eslint-disable-next-line @typescript-eslint/naming-convention var DataTypes = this.internal.getContext().DataTypes; // eslint-disable-next-line no-async-promise-executor return new Promise(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () { var requestType, count_1, expected_1, isDone_1, cid_1, requestData; var _this = this; return __generator(this, function (_a) { switch (_a.label) { case 0: if (!this.internal.checkConnected(reject)) return [3 /*break*/, 2]; try { require_non_null_1.requireNonNull(path, 'Message path'); require_non_null_1.requireNonNull(filter, 'Session filter'); require_non_null_1.requireNonNull(request, 'Request'); require_non_null_1.requireNonNull(callback, 'Response callback'); } catch (e) { reject(e); return [2 /*return*/]; } requestType = void 0; try { requestType = reqType ? DataTypes.getChecked(reqType) : DataTypes.getByValue(request); } catch (e) { reject(e); return [2 /*return*/]; } if (requestType === undefined) { reject(new Error('Could not determine request type')); return [2 /*return*/]; } count_1 = 0; expected_1 = -1; isDone_1 = function () { return (count_1 >= expected_1 && expected_1 !== -1) || expected_1 === 0; }; return [4 /*yield*/, this.internal.getConversationSet().newConversation({ onOpen: function () { // no-op }, onResponse: function (conversationId, response) { if (response === null) { return true; } if (typeof response === 'number') { expected_1 = response; return isDone_1(); } var sessionID = response.sessionID; count_1++; if (response.errorReason) { callback.onResponseError(sessionID, response.errorReason); } else { var responseDataType = DataTypes.getByName(response.response.responseDataType); var responseType = void 0; try { responseType = DataTypes.getChecked(respType ? respType : response.response.responseDataType); } catch (e) { callback.onResponseError(sessionID, e); return isDone_1(); } if (responseDataType.canReadAs(responseType.valueClass)) { var value = void 0; try { value = responseDataType.readAs(responseType.valueClass, response.response.data); } catch (e) { callback.onResponseError(sessionID, e); return isDone_1(); } try { callback.onResponse(sessionID, value); } catch (e) { log.error('Exception within messaging filter callback', e); } } else { callback.onResponseError(sessionID, new Error("The received response was a " + responseDataType + "," + (" which cannot be read as: " + responseType))); } } return isDone_1(); }, onDiscard: function () { // no-op } })]; case 1: cid_1 = _a.sent(); log.debug('Sending filter request to server', request); requestData = void 0; try { requestData = requestType.writeValueToArray(request); } catch (e) { reject(e); return [2 /*return*/]; } this.MESSAGING_FILTER_SEND.send({ cid: cid_1, path: path, filter: filter, dataType: requestType, request: requestData }, function (err, response) { if (!response_success_1.responseSuccess(err, response)) { log.debug('Request failed', path); _this.internal.getConversationSet().respondIfPresent(cid_1, null); reject(err); } else if (response.isSuccess) { log.debug('Request complete', path); _this.internal.getConversationSet().respondIfPresent(cid_1, response.numberSent); resolve(response.numberSent); } else { log.debug('Request failed', path); _this.internal.getConversationSet().respondIfPresent(cid_1, null); reject(response.errors); } }); _a.label = 2; case 2: return [2 /*return*/]; } }); }); }); }; /** * @inheritdoc */ MessagesImpl.prototype.setRequestStream = function (path, stream, reqType, resType) { // eslint-disable-next-line @typescript-eslint/naming-convention var DataTypes = this.internal.getContext().DataTypes; var proxy = new RequestStreamProxy(reqType ? DataTypes.getValueClassChecked(reqType) : undefined, resType ? DataTypes.getValueClassChecked(resType) : undefined, stream); var old = this.requestStreams[path]; this.requestStreams[path] = proxy; return old ? old.stream : undefined; }; /** * @inheritdoc */ MessagesImpl.prototype.removeRequestStream = function (path) { var proxy = this.requestStreams[path]; delete this.requestStreams[path]; return proxy ? proxy.stream : undefined; }; return MessagesImpl; }()); exports.MessagesImpl = MessagesImpl;