@dolittle/sdk.services
Version:
Dolittle is a decentralized, distributed, event-driven microservice platform built to harness the power of events.
187 lines • 18.5 kB
JavaScript
"use strict";
// Copyright (c) Dolittle. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
Object.defineProperty(exports, "__esModule", { value: true });
exports.ReverseCallClient = void 0;
const rxjs_1 = require("rxjs");
const operators_1 = require("rxjs/operators");
const rudiments_1 = require("@dolittle/rudiments");
const sdk_protobuf_1 = require("@dolittle/sdk.protobuf");
const Ping_pb_1 = require("@dolittle/contracts/Services/Ping_pb");
const ReverseCallContext_pb_1 = require("@dolittle/contracts/Services/ReverseCallContext_pb");
const duration_pb_1 = require("google-protobuf/google/protobuf/duration_pb");
const DidNotReceiveConnectResponse_1 = require("./DidNotReceiveConnectResponse");
const IReverseCallClient_1 = require("./IReverseCallClient");
const PingTimeout_1 = require("./PingTimeout");
/**
* Represents an implementation of {@link IReverseCallClient}.
* @template TClientMessage The type of the messages from the client to the server.
* @template TServerMessage The type of the messages from the server to the client.
* @template TConnectArguments The type of the connect arguments message.
* @template TConnectResponse The type of the connect response message.
* @template TRequest The type of the request messages.
* @template TResponse The type of the response messages.
*/
class ReverseCallClient extends IReverseCallClient_1.IReverseCallClient {
/**
* Creates a new instance of the {@link ReverseCallClient} class.
* @param {(Observable, Cancellation) => Observable<TServerMessage>} _establishConnection - The callback to use to establish a new connection.
* @param {() => TClientMessage} _messageConstructor - The constructor to use to create a new {@link TClientMessage}.
* @param {(TClientMessage, TConnectArguments) => void} _setConnectArguments - The callback to use to set the connect arguments to a client message.
* @param {(TServerMessage) => TRequest | undefined} _getConnectResponse - The callback to use to get the connect response from a server message.
* @param {(TServerMessage) => TRequest | undefined} _getMessageRequest - The callback to use to get a request from a server message.
* @param {(TClientMessage, TResponse) => void} _setMessageResponse - The callback to use to set a response to a client message.
* @param {(TConnectArguments, ReverseCallArgumentsContext) => void} _setArgumentsContext - The callback to use to set the call context in a connect arguments message.
* @param {(TRequest) => ReverseCallRequestContext | undefined} _getRequestContext - The callback to use to get the call context from a request message.
* @param {(TResponse, ReverseCallResponseContext) => void} _setResponseContext - The callback to use to set the call context in a response message.
* @param {(TServerMessage) => Ping | undefined} _getMessagePing - The callback to use to get a ping from a server message.
* @param {(TClientMessage, Pong) => void} _setMessagePong - The callback to use to set a pong in a client message.
* @param {ExecutionContext} _executionContext - The execution context of the client.
* @param {TConnectArguments} _connectArguments - The connect arguments to use to initiate the reverse call client.
* @param {number} _pingInterval - The interval to request the server to send pings (in seconds).
* @param {ReverseCallCallback<TRequest, TResponse>} _callback - The callback to use to handle requests from the server to the client.
* @param {Cancellation} _cancellation - The cancellation token to use to cancel the reverse call client.
* @param {Logger} _logger - The logger to use for logging.
*/
constructor(_establishConnection, _messageConstructor, _setConnectArguments, _getConnectResponse, _getMessageRequest, _setMessageResponse, _setArgumentsContext, _getRequestContext, _setResponseContext, _getMessagePing, _setMessagePong, _executionContext, _connectArguments, _pingInterval, _callback, _cancellation, _logger) {
super((subscriber) => this.start(subscriber));
this._establishConnection = _establishConnection;
this._messageConstructor = _messageConstructor;
this._setConnectArguments = _setConnectArguments;
this._getConnectResponse = _getConnectResponse;
this._getMessageRequest = _getMessageRequest;
this._setMessageResponse = _setMessageResponse;
this._setArgumentsContext = _setArgumentsContext;
this._getRequestContext = _getRequestContext;
this._setResponseContext = _setResponseContext;
this._getMessagePing = _getMessagePing;
this._setMessagePong = _setMessagePong;
this._executionContext = _executionContext;
this._connectArguments = _connectArguments;
this._pingInterval = _pingInterval;
this._callback = _callback;
this._cancellation = _cancellation;
this._logger = _logger;
}
/**
* Sets up the connection, pinging and response handling from the set/get methods.
* @param {Subscriber<TConnectResponse>} subscriber - The subscriber that started the reverse call.
*/
start(subscriber) {
const callContext = new ReverseCallContext_pb_1.ReverseCallArgumentsContext();
callContext.setHeadid(sdk_protobuf_1.Guids.toProtobuf(rudiments_1.Guid.create()));
const pingInterval = new duration_pb_1.Duration();
const pingSeconds = Math.trunc(this._pingInterval);
const pingNanos = Math.trunc((this._pingInterval - pingSeconds) * 1e9);
pingInterval.setSeconds(pingSeconds);
pingInterval.setNanos(pingNanos);
callContext.setPinginterval(pingInterval);
callContext.setExecutioncontext(sdk_protobuf_1.ExecutionContexts.toProtobuf(this._executionContext));
this._setArgumentsContext(this._connectArguments, callContext);
const clientMessage = new this._messageConstructor();
this._setConnectArguments(clientMessage, this._connectArguments);
const clientToServerMessages = new rxjs_1.Subject();
const serverToClientMessages = this._establishConnection(clientToServerMessages, this._cancellation);
const [pings, requests] = (0, rxjs_1.partition)(serverToClientMessages.pipe((0, operators_1.filter)(this.isPingOrRequests, this), (0, operators_1.timeout)(this._pingInterval * 3e3)), this.isPingMessage, this);
const pongs = pings.pipe((0, operators_1.map)((message) => {
const responseMessage = new this._messageConstructor();
const pong = new Ping_pb_1.Pong();
this._setMessagePong(responseMessage, pong);
return responseMessage;
}));
const responses = new rxjs_1.Subject();
requests
.pipe((0, operators_1.filter)(this.isValidMessage, this))
.subscribe(this.handleServerRequests(responses));
// write pongs and responses to the Runtime
(0, rxjs_1.merge)(pongs, responses).subscribe(clientToServerMessages);
const connectResponse = serverToClientMessages.pipe((0, operators_1.filter)(this.isConnectResponse, this));
const errorsAndCompletion = serverToClientMessages.pipe((0, operators_1.filter)(() => false));
(0, rxjs_1.concat)(connectResponse, errorsAndCompletion)
.subscribe(this.handleConnectionResponse(subscriber));
// send the connection request
clientToServerMessages.next(clientMessage);
}
isPingOrRequests(message) {
const ping = this._getMessagePing(message);
const request = this._getMessageRequest(message);
if (ping || request) {
return true;
}
const connect = this._getConnectResponse(message);
if (!connect) {
this._logger.warn('Received message from Reverse Call Dispatcher, but it was not a request, ping or a connection response');
}
return false;
}
isPingMessage(message) {
return !!this._getMessagePing(message);
}
isValidMessage(message) {
const request = this._getMessageRequest(message);
const context = this._getRequestContext(request);
if (!context) {
this._logger.warn('Received request from Reverse Call Dispatcher, but it did not contain a Reverse Call Context');
return false;
}
if (!context.hasExecutioncontext()) {
this._logger.warn('Received request from Reverse Call Dispatcher, but it did not contain an Execution Context');
return false;
}
return true;
}
handleServerRequests(responses) {
return {
next: async (message) => {
try {
const request = this._getMessageRequest(message);
const context = this._getRequestContext(request);
const requestContext = sdk_protobuf_1.ExecutionContexts.toSDK(context.getExecutioncontext());
const executionContext = this._executionContext
.forTenant(requestContext.tenantId.value)
.forClaims(requestContext.claims);
const response = await this._callback(request, executionContext);
const responseContext = new ReverseCallContext_pb_1.ReverseCallResponseContext();
responseContext.setCallid(context.getCallid());
this._setResponseContext(response, responseContext);
const responseMessage = new this._messageConstructor();
this._setMessageResponse(responseMessage, response);
responses.next(responseMessage);
}
catch (error) {
responses.error(error);
}
},
error: (error) => {
responses.error(error);
}
};
}
isConnectResponse(message) {
return !!this._getConnectResponse(message);
}
handleConnectionResponse(subscriber) {
return {
next: (message) => {
const response = this._getConnectResponse(message);
if (!response) {
subscriber.error(new DidNotReceiveConnectResponse_1.DidNotReceiveConnectResponse());
return;
}
subscriber.next(response);
},
error: (error) => {
if (error instanceof rxjs_1.TimeoutError) {
subscriber.error(new PingTimeout_1.PingTimeout());
return;
}
subscriber.error(error);
},
complete: () => {
subscriber.complete();
},
};
}
}
exports.ReverseCallClient = ReverseCallClient;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiUmV2ZXJzZUNhbGxDbGllbnQuanMiLCJzb3VyY2VSb290IjoiLi4vIiwic291cmNlcyI6WyJSZXZlcnNlQ2FsbENsaWVudC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUEsK0NBQStDO0FBQy9DLHFHQUFxRzs7O0FBR3JHLCtCQUE4RztBQUM5Ryw4Q0FBc0Q7QUFDdEQsbURBQTJDO0FBRzNDLHlEQUFrRTtBQUdsRSxrRUFBa0U7QUFDbEUsOEZBQXdKO0FBQ3hKLDZFQUF1RTtBQUV2RSxpRkFBOEU7QUFDOUUsNkRBQStFO0FBQy9FLCtDQUE0QztBQUU1Qzs7Ozs7Ozs7R0FRRztBQUNILE1BQWEsaUJBQTRHLFNBQVEsdUNBQW9DO0lBQ2pLOzs7Ozs7Ozs7Ozs7Ozs7Ozs7O09BbUJHO0lBQ0gsWUFDWSxvQkFBc0gsRUFDdEgsbUJBQTZDLEVBQzdDLG9CQUE0RixFQUM1RixtQkFBOEUsRUFDOUUsa0JBQXFFLEVBQ3JFLG1CQUEwRSxFQUMxRSxvQkFBeUcsRUFDekcsa0JBQWdGLEVBQ2hGLG1CQUF1RixFQUN2RixlQUE4RCxFQUM5RCxlQUE4RCxFQUM5RCxpQkFBbUMsRUFDbkMsaUJBQW9DLEVBQ3BDLGFBQXFCLEVBQ3JCLFNBQW1ELEVBQ25ELGFBQTJCLEVBQzNCLE9BQWU7UUFFdkIsS0FBSyxDQUFDLENBQUMsVUFBVSxFQUFFLEVBQUUsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLFVBQVUsQ0FBQyxDQUFDLENBQUM7UUFsQnRDLHlCQUFvQixHQUFwQixvQkFBb0IsQ0FBa0c7UUFDdEgsd0JBQW1CLEdBQW5CLG1CQUFtQixDQUEwQjtRQUM3Qyx5QkFBb0IsR0FBcEIsb0JBQW9CLENBQXdFO1FBQzVGLHdCQUFtQixHQUFuQixtQkFBbUIsQ0FBMkQ7UUFDOUUsdUJBQWtCLEdBQWxCLGtCQUFrQixDQUFtRDtRQUNyRSx3QkFBbUIsR0FBbkIsbUJBQW1CLENBQXVEO1FBQzFFLHlCQUFvQixHQUFwQixvQkFBb0IsQ0FBcUY7UUFDekcsdUJBQWtCLEdBQWxCLGtC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