diffusion
Version:
Diffusion JavaScript client
618 lines (617 loc) • 29.5 kB
JavaScript
"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;