/*
* Copyright 2023, TeamDev. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Redistribution and use in source and/or binary forms, with or without
* modification, must retain the above copyright notice and the following
* disclaimer.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
"use strict";
import {TypedMessage} from './typed-message';
import {Subscriptions} from '../proto/spine/web/keeping_up_pb';
import {HttpResponseHandler} from "./http-response-handler";
/**
* @typedef {Object} SubscriptionRouting
*
* @property {string} create
* the name of the subscription creation endpoint; defaults to "/subscription/create"
* @property {string} keepUp
* the name of the subscription keep up endpoint; defaults to "/subscription/keep-up"
* @property {string} keepUpAll
* the name of the subscription bulk keep up endpoint; defaults to "/subscription/keep-up-all"
* @property {string} cancel
* the name of the subscription cancellation endpoint; defaults to "/subscription/cancel"
* @property {string} cancelAll
* the name of the subscription bulk cancellation endpoint; defaults to "/subscription/cancel-all"
*/
/**
* @typedef {Object} Routing
*
* @property {string} query
* the name of the query endpoint; defaults to "/query"
* @property {string} command
* the name of the command endpoint; defaults to "/command"
* @property {!SubscriptionRouting} subscription
* the config of the subscription endpoints
*/
class Endpoint {
/**
* Sends a command to the endpoint.
*
* @param {!TypedMessage<Command>} command a Command to send to the Spine server
* @return {Promise<Object>} a promise of a successful server response, rejected if
* an error occurs
*/
command(command) {
return this._executeCommand(command);
}
/**
* Sends a query to the endpoint.
*
* @param {!spine.client.Query} query a Query to Spine server to retrieve some domain entities
* @return {Promise<Object>} a promise of a successful server response, rejected if
* an error occurs
*/
query(query) {
const typedQuery = TypedMessage.of(query);
return this._performQuery(typedQuery);
}
/**
* Sends a request to subscribe to a provided topic to an endpoint.
*
* @param {!spine.client.Topic} topic a topic for which a subscription is created
* @return {Promise<Object>} a promise of a successful server response, rejected if
* an error occurs
*/
subscribeTo(topic) {
const typedTopic = TypedMessage.of(topic);
return this._subscribeTo(typedTopic);
}
/**
* Sends a request to keep a subscription, stopping it from being closed by server.
*
* @param {!spine.client.Subscription} subscription a subscription that should be kept open
* @returns {Promise<Object>} a promise of a successful server response, rejected if
* an error occurs
*/
keepUpSingleSubscription(subscription) {
const typedSubscription = TypedMessage.of(subscription);
return this._keepUp(typedSubscription);
}
/**
* Sends a request to keep up several subscriptions, preventing them
* from being closed by the server.
*
* @param {!Array<spine.client.Subscription>} subscriptions subscriptions that should be kept open
* @return {Promise<Object>} a promise of a successful server response, rejected if
* an error occurs
*/
keepUpSubscriptions(subscriptions) {
return this._keepUpAll(subscriptions);
}
/**
* Sends a request to cancel an existing subscription.
*
* Cancelling subscription stops the server from updating subscription with new values.
*
* @param {!spine.client.Subscription} subscription a subscription that should be kept open
* @return {Promise<Object>} a promise of a successful server response, rejected if
* an error occurs
*/
cancelSubscription(subscription) {
const typedSubscription = TypedMessage.of(subscription);
return this._cancel(typedSubscription);
}
/**
* Sends a request to cancel all the given subscriptions.
*
* Cancelling subscriptions stops the server from updating subscription with new values.
*
* @param {!Array<spine.client.Subscription>>} subscriptions subscriptions that should
* be cancelled
* @return {Promise<Object>} a promise of a successful server response, rejected if
* an error occurs
*/
cancelAll(subscriptions) {
return this._cancelAll(subscriptions);
}
/**
* @param {!TypedMessage<Command>} command a Command to send to the Spine server
* @return {Promise<Object>} a promise of a successful server response, rejected if
* an error occurs
* @protected
* @abstract
*/
_executeCommand(command) {
throw new Error('Not implemented in abstract base.');
}
/**
* @param {!TypedMessage<Query>} query a Query to Spine server to retrieve some domain entities
* @return {Promise<Object>} a promise of a successful server response, rejected if
* an error occurs
* @protected
* @abstract
*/
_performQuery(query) {
throw new Error('Not implemented in abstract base.');
}
/**
* @param {!TypedMessage<spine.client.Topic>} topic a topic to create a subscription for
* @return {Promise<Object>} a promise of a successful server response, rejected if
* an error occurs
* @protected
* @abstract
*/
_subscribeTo(topic) {
throw new Error('Not implemented in abstract base.');
}
/**
* @param {!TypedMessage<spine.client.Subscription>} subscription a subscription to keep alive
* @return {Promise<Object>} a promise of a successful server response, rejected if
* an error occurs
* @protected
* @abstract
*/
_keepUp(subscription) {
throw new Error('Not implemented in abstract base.');
}
/**
* @param {!Array<TypedMessage<spine.client.Subscription>>} subscriptions subscriptions to keep up
* @return {Promise<Object>} a promise of a successful server response, rejected if
* an error occurs
* @protected
* @abstract
*/
_keepUpAll(subscriptions) {
throw new Error('Not implemented in abstract base.');
}
/**
* @param {!TypedMessage<spine.client.Subscription>} subscription a subscription to be canceled
* @return {Promise<Object>} a promise of a successful server response, rejected if
* an error occurs
* @protected
* @abstract
*/
_cancel(subscription) {
throw new Error('Not implemented in abstract base.');
}
/**
* @param {!Array<spine.client.Subscription>} subscriptions subscriptions to be canceled
* @return {Promise<Object>} a promise of a successful server response, rejected if
* an error occurs
* @protected
* @abstract
*/
_cancelAll(subscriptions) {
throw new Error('Not implemented in abstract base.');
}
}
/**
* Spine HTTP endpoint which is used to send Commands and Queries using
* the provided HTTP client.
*/
export class HttpEndpoint extends Endpoint {
/**
* @param {!HttpClient} httpClient a client sending requests to server
* @param {!HttpResponseHandler} responseHandler a handle for the HTTP responses from server
* @param {Routing} routing endpoint routing parameters
*/
constructor(httpClient, responseHandler, routing) {
super();
this._httpClient = httpClient;
this._routing = routing;
this._responseHandler = responseHandler;
}
/**
* Sends a command to the endpoint.
*
* @param {!TypedMessage<Command>} command a Command to send to the Spine server
* @return {Promise<Object|SpineError>} a promise of a successful server response JSON data,
* rejected if the client response is not `2xx`,
* or a connection error occurs
* @protected
*/
_executeCommand(command) {
const path = (this._routing && this._routing.command) || '/command';
return this._sendMessage(path, command);
}
/**
* Sends a query to the endpoint.
*
* @param {!TypedMessage<Query>} query a Query to Spine server to retrieve some domain entities
* @return {Promise<Object|SpineError>} a promise of a successful server response JSON data,
* rejected if the client response is not `2xx`,
* or a connection error occurs
* @protected
*/
_performQuery(query) {
const path = (this._routing && this._routing.query) || '/query';
return this._sendMessage(path, query);
}
/**
* Sends a request to create a subscription for a topic.
*
* @param {!TypedMessage<spine.client.Topic>} topic a topic to subscribe to
* @return {Promise<Object|SpineError>} a promise of a successful server response JSON data,
* rejected if the client response is not `2xx`,
* or a connection error occurs
* @protected
*/
_subscribeTo(topic) {
const path = (this._routing && this._routing.subscription && this._routing.subscription.create)
|| '/subscription/create';
return this._sendMessage(path, topic);
}
/**
* Sends a request to keep alive the given subscription.
*
* @param {!TypedMessage<spine.client.Subscription>} subscription a subscription that is prevented
* from being closed by server
* @return {Promise<Object|SpineError>} a promise of a successful server response JSON data,
* rejected if the client response is not `2xx`,
* or a connection error occurs
* @protected
*/
_keepUp(subscription) {
const path = (this._routing && this._routing.subscription && this._routing.subscription.keepUp)
|| '/subscription/keep-up';
return this._sendMessage(path, subscription);
}
/**
* Sends a request to keep alive the given subscriptions.
*
* @param {!Array<spine.client.Subscription>} subscriptions subscriptions that are prevented
* from being closed by the server
* @return {Promise<Object|SpineError>} a promise of a successful server response JSON data,
* rejected if the client response is not `2xx`,
* or a connection error occurs
* @protected
*/
_keepUpAll(subscriptions) {
const path = (this._routing && this._routing.subscription && this._routing.subscription.keepUpAll)
|| '/subscription/keep-up-all';
const request = new Subscriptions()
request.setSubscriptionList(subscriptions);
const typed = TypedMessage.of(request);
return this._sendMessage(path, typed);
}
/**
* Sends a request to cancel the given subscription.
*
* @param {!TypedMessage<spine.client.Subscription>} subscription a subscription to be canceled
* @return {Promise<Object|SpineError>} a promise of a successful server response JSON data,
* rejected if the client response is not `2xx`,
* or a connection error occurs
* @protected
*/
_cancel(subscription) {
const path = (this._routing && this._routing.subscription && this._routing.subscription.cancel)
|| '/subscription/cancel';
return this._sendMessage(path, subscription);
}
/**
* Sends a request to cancel the given subscriptions.
*
* @param {!Array<spine.client.Subscription>} subscriptions subscriptions to be canceled
* @return {Promise<Object|SpineError>} a promise of a successful server response JSON data,
* rejected if the client response is not `2xx`,
* or a connection error occurs
* @protected
*/
_cancelAll(subscriptions) {
const path = (this._routing && this._routing.subscription && this._routing.subscription.cancelAll)
|| '/subscription/cancel-all';
const request = new Subscriptions();
request.setSubscriptionList(subscriptions);
const typed = TypedMessage.of(request);
return this._sendMessage(path, typed);
}
/**
* Sends the given message to the given endpoint.
*
* @param {!string} endpoint an endpoint to send the message to
* @param {!TypedMessage} message a message to send, as a {@link TypedMessage}
* @return {Promise<Object|SpineError>} a promise of a successful server response JSON data,
* rejected if the client response is not `2xx`,
* or a connection error occurs
* @private
*/
_sendMessage(endpoint, message) {
return new Promise((resolve, reject) => {
this._httpClient
.postMessage(endpoint, message)
.then(this._responseHandler.handle
.bind(this._responseHandler),
this._responseHandler.onConnectionError
.bind(this._responseHandler))
.then(resolve, reject);
});
}
}