UNPKG

@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
"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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