Source: client/http-endpoint.js

  1. /*
  2. * Copyright 2023, TeamDev. All rights reserved.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Redistribution and use in source and/or binary forms, with or without
  11. * modification, must retain the above copyright notice and the following
  12. * disclaimer.
  13. *
  14. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  15. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  16. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  17. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  18. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  19. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  20. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  21. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  22. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  23. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  24. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  25. */
  26. "use strict";
  27. import {TypedMessage} from './typed-message';
  28. import {Subscriptions} from '../proto/spine/web/keeping_up_pb';
  29. import {HttpResponseHandler} from "./http-response-handler";
  30. /**
  31. * @typedef {Object} SubscriptionRouting
  32. *
  33. * @property {string} create
  34. * the name of the subscription creation endpoint; defaults to "/subscription/create"
  35. * @property {string} keepUp
  36. * the name of the subscription keep up endpoint; defaults to "/subscription/keep-up"
  37. * @property {string} keepUpAll
  38. * the name of the subscription bulk keep up endpoint; defaults to "/subscription/keep-up-all"
  39. * @property {string} cancel
  40. * the name of the subscription cancellation endpoint; defaults to "/subscription/cancel"
  41. * @property {string} cancelAll
  42. * the name of the subscription bulk cancellation endpoint; defaults to "/subscription/cancel-all"
  43. */
  44. /**
  45. * @typedef {Object} Routing
  46. *
  47. * @property {string} query
  48. * the name of the query endpoint; defaults to "/query"
  49. * @property {string} command
  50. * the name of the command endpoint; defaults to "/command"
  51. * @property {!SubscriptionRouting} subscription
  52. * the config of the subscription endpoints
  53. */
  54. class Endpoint {
  55. /**
  56. * Sends a command to the endpoint.
  57. *
  58. * @param {!TypedMessage<Command>} command a Command to send to the Spine server
  59. * @return {Promise<Object>} a promise of a successful server response, rejected if
  60. * an error occurs
  61. */
  62. command(command) {
  63. return this._executeCommand(command);
  64. }
  65. /**
  66. * Sends a query to the endpoint.
  67. *
  68. * @param {!spine.client.Query} query a Query to Spine server to retrieve some domain entities
  69. * @return {Promise<Object>} a promise of a successful server response, rejected if
  70. * an error occurs
  71. */
  72. query(query) {
  73. const typedQuery = TypedMessage.of(query);
  74. return this._performQuery(typedQuery);
  75. }
  76. /**
  77. * Sends a request to subscribe to a provided topic to an endpoint.
  78. *
  79. * @param {!spine.client.Topic} topic a topic for which a subscription is created
  80. * @return {Promise<Object>} a promise of a successful server response, rejected if
  81. * an error occurs
  82. */
  83. subscribeTo(topic) {
  84. const typedTopic = TypedMessage.of(topic);
  85. return this._subscribeTo(typedTopic);
  86. }
  87. /**
  88. * Sends a request to keep a subscription, stopping it from being closed by server.
  89. *
  90. * @param {!spine.client.Subscription} subscription a subscription that should be kept open
  91. * @returns {Promise<Object>} a promise of a successful server response, rejected if
  92. * an error occurs
  93. */
  94. keepUpSingleSubscription(subscription) {
  95. const typedSubscription = TypedMessage.of(subscription);
  96. return this._keepUp(typedSubscription);
  97. }
  98. /**
  99. * Sends a request to keep up several subscriptions, preventing them
  100. * from being closed by the server.
  101. *
  102. * @param {!Array<spine.client.Subscription>} subscriptions subscriptions that should be kept open
  103. * @return {Promise<Object>} a promise of a successful server response, rejected if
  104. * an error occurs
  105. */
  106. keepUpSubscriptions(subscriptions) {
  107. return this._keepUpAll(subscriptions);
  108. }
  109. /**
  110. * Sends a request to cancel an existing subscription.
  111. *
  112. * Cancelling subscription stops the server from updating subscription with new values.
  113. *
  114. * @param {!spine.client.Subscription} subscription a subscription that should be kept open
  115. * @return {Promise<Object>} a promise of a successful server response, rejected if
  116. * an error occurs
  117. */
  118. cancelSubscription(subscription) {
  119. const typedSubscription = TypedMessage.of(subscription);
  120. return this._cancel(typedSubscription);
  121. }
  122. /**
  123. * Sends a request to cancel all the given subscriptions.
  124. *
  125. * Cancelling subscriptions stops the server from updating subscription with new values.
  126. *
  127. * @param {!Array<spine.client.Subscription>>} subscriptions subscriptions that should
  128. * be cancelled
  129. * @return {Promise<Object>} a promise of a successful server response, rejected if
  130. * an error occurs
  131. */
  132. cancelAll(subscriptions) {
  133. return this._cancelAll(subscriptions);
  134. }
  135. /**
  136. * @param {!TypedMessage<Command>} command a Command to send to the Spine server
  137. * @return {Promise<Object>} a promise of a successful server response, rejected if
  138. * an error occurs
  139. * @protected
  140. * @abstract
  141. */
  142. _executeCommand(command) {
  143. throw new Error('Not implemented in abstract base.');
  144. }
  145. /**
  146. * @param {!TypedMessage<Query>} query a Query to Spine server to retrieve some domain entities
  147. * @return {Promise<Object>} a promise of a successful server response, rejected if
  148. * an error occurs
  149. * @protected
  150. * @abstract
  151. */
  152. _performQuery(query) {
  153. throw new Error('Not implemented in abstract base.');
  154. }
  155. /**
  156. * @param {!TypedMessage<spine.client.Topic>} topic a topic to create a subscription for
  157. * @return {Promise<Object>} a promise of a successful server response, rejected if
  158. * an error occurs
  159. * @protected
  160. * @abstract
  161. */
  162. _subscribeTo(topic) {
  163. throw new Error('Not implemented in abstract base.');
  164. }
  165. /**
  166. * @param {!TypedMessage<spine.client.Subscription>} subscription a subscription to keep alive
  167. * @return {Promise<Object>} a promise of a successful server response, rejected if
  168. * an error occurs
  169. * @protected
  170. * @abstract
  171. */
  172. _keepUp(subscription) {
  173. throw new Error('Not implemented in abstract base.');
  174. }
  175. /**
  176. * @param {!Array<TypedMessage<spine.client.Subscription>>} subscriptions subscriptions to keep up
  177. * @return {Promise<Object>} a promise of a successful server response, rejected if
  178. * an error occurs
  179. * @protected
  180. * @abstract
  181. */
  182. _keepUpAll(subscriptions) {
  183. throw new Error('Not implemented in abstract base.');
  184. }
  185. /**
  186. * @param {!TypedMessage<spine.client.Subscription>} subscription a subscription to be canceled
  187. * @return {Promise<Object>} a promise of a successful server response, rejected if
  188. * an error occurs
  189. * @protected
  190. * @abstract
  191. */
  192. _cancel(subscription) {
  193. throw new Error('Not implemented in abstract base.');
  194. }
  195. /**
  196. * @param {!Array<spine.client.Subscription>} subscriptions subscriptions to be canceled
  197. * @return {Promise<Object>} a promise of a successful server response, rejected if
  198. * an error occurs
  199. * @protected
  200. * @abstract
  201. */
  202. _cancelAll(subscriptions) {
  203. throw new Error('Not implemented in abstract base.');
  204. }
  205. }
  206. /**
  207. * Spine HTTP endpoint which is used to send Commands and Queries using
  208. * the provided HTTP client.
  209. */
  210. export class HttpEndpoint extends Endpoint {
  211. /**
  212. * @param {!HttpClient} httpClient a client sending requests to server
  213. * @param {!HttpResponseHandler} responseHandler a handle for the HTTP responses from server
  214. * @param {Routing} routing endpoint routing parameters
  215. */
  216. constructor(httpClient, responseHandler, routing) {
  217. super();
  218. this._httpClient = httpClient;
  219. this._routing = routing;
  220. this._responseHandler = responseHandler;
  221. }
  222. /**
  223. * Sends a command to the endpoint.
  224. *
  225. * @param {!TypedMessage<Command>} command a Command to send to the Spine server
  226. * @return {Promise<Object|SpineError>} a promise of a successful server response JSON data,
  227. * rejected if the client response is not `2xx`,
  228. * or a connection error occurs
  229. * @protected
  230. */
  231. _executeCommand(command) {
  232. const path = (this._routing && this._routing.command) || '/command';
  233. return this._sendMessage(path, command);
  234. }
  235. /**
  236. * Sends a query to the endpoint.
  237. *
  238. * @param {!TypedMessage<Query>} query a Query to Spine server to retrieve some domain entities
  239. * @return {Promise<Object|SpineError>} a promise of a successful server response JSON data,
  240. * rejected if the client response is not `2xx`,
  241. * or a connection error occurs
  242. * @protected
  243. */
  244. _performQuery(query) {
  245. const path = (this._routing && this._routing.query) || '/query';
  246. return this._sendMessage(path, query);
  247. }
  248. /**
  249. * Sends a request to create a subscription for a topic.
  250. *
  251. * @param {!TypedMessage<spine.client.Topic>} topic a topic to subscribe to
  252. * @return {Promise<Object|SpineError>} a promise of a successful server response JSON data,
  253. * rejected if the client response is not `2xx`,
  254. * or a connection error occurs
  255. * @protected
  256. */
  257. _subscribeTo(topic) {
  258. const path = (this._routing && this._routing.subscription && this._routing.subscription.create)
  259. || '/subscription/create';
  260. return this._sendMessage(path, topic);
  261. }
  262. /**
  263. * Sends a request to keep alive the given subscription.
  264. *
  265. * @param {!TypedMessage<spine.client.Subscription>} subscription a subscription that is prevented
  266. * from being closed by server
  267. * @return {Promise<Object|SpineError>} a promise of a successful server response JSON data,
  268. * rejected if the client response is not `2xx`,
  269. * or a connection error occurs
  270. * @protected
  271. */
  272. _keepUp(subscription) {
  273. const path = (this._routing && this._routing.subscription && this._routing.subscription.keepUp)
  274. || '/subscription/keep-up';
  275. return this._sendMessage(path, subscription);
  276. }
  277. /**
  278. * Sends a request to keep alive the given subscriptions.
  279. *
  280. * @param {!Array<spine.client.Subscription>} subscriptions subscriptions that are prevented
  281. * from being closed by the server
  282. * @return {Promise<Object|SpineError>} a promise of a successful server response JSON data,
  283. * rejected if the client response is not `2xx`,
  284. * or a connection error occurs
  285. * @protected
  286. */
  287. _keepUpAll(subscriptions) {
  288. const path = (this._routing && this._routing.subscription && this._routing.subscription.keepUpAll)
  289. || '/subscription/keep-up-all';
  290. const request = new Subscriptions()
  291. request.setSubscriptionList(subscriptions);
  292. const typed = TypedMessage.of(request);
  293. return this._sendMessage(path, typed);
  294. }
  295. /**
  296. * Sends a request to cancel the given subscription.
  297. *
  298. * @param {!TypedMessage<spine.client.Subscription>} subscription a subscription to be canceled
  299. * @return {Promise<Object|SpineError>} a promise of a successful server response JSON data,
  300. * rejected if the client response is not `2xx`,
  301. * or a connection error occurs
  302. * @protected
  303. */
  304. _cancel(subscription) {
  305. const path = (this._routing && this._routing.subscription && this._routing.subscription.cancel)
  306. || '/subscription/cancel';
  307. return this._sendMessage(path, subscription);
  308. }
  309. /**
  310. * Sends a request to cancel the given subscriptions.
  311. *
  312. * @param {!Array<spine.client.Subscription>} subscriptions subscriptions to be canceled
  313. * @return {Promise<Object|SpineError>} a promise of a successful server response JSON data,
  314. * rejected if the client response is not `2xx`,
  315. * or a connection error occurs
  316. * @protected
  317. */
  318. _cancelAll(subscriptions) {
  319. const path = (this._routing && this._routing.subscription && this._routing.subscription.cancelAll)
  320. || '/subscription/cancel-all';
  321. const request = new Subscriptions();
  322. request.setSubscriptionList(subscriptions);
  323. const typed = TypedMessage.of(request);
  324. return this._sendMessage(path, typed);
  325. }
  326. /**
  327. * Sends the given message to the given endpoint.
  328. *
  329. * @param {!string} endpoint an endpoint to send the message to
  330. * @param {!TypedMessage} message a message to send, as a {@link TypedMessage}
  331. * @return {Promise<Object|SpineError>} a promise of a successful server response JSON data,
  332. * rejected if the client response is not `2xx`,
  333. * or a connection error occurs
  334. * @private
  335. */
  336. _sendMessage(endpoint, message) {
  337. return new Promise((resolve, reject) => {
  338. this._httpClient
  339. .postMessage(endpoint, message)
  340. .then(this._responseHandler.handle
  341. .bind(this._responseHandler),
  342. this._responseHandler.onConnectionError
  343. .bind(this._responseHandler))
  344. .then(resolve, reject);
  345. });
  346. }
  347. }