Source: client/client-factory.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 {ActorRequestFactory} from "./actor-request-factory";
  28. import {Client} from './client';
  29. import {CommandingClient} from "./commanding-client";
  30. import {HttpClient} from "./http-client";
  31. import {HttpEndpoint} from "./http-endpoint";
  32. import {HttpResponseHandler} from "./http-response-handler";
  33. /**
  34. * @typedef {Object} ClientOptions a type of object for initialization of Spine client
  35. *
  36. * @property {!Array<Object>} protoIndexFiles
  37. * the list of the `index.js` files generated by {@link https://github.com/SpineEventEngine/base/tree/master/tools/proto-js-plugin the Protobuf plugin for JS}
  38. * @property {?string} endpointUrl
  39. * the URL of the Spine-based backend endpoint
  40. * @property {?HttpClient} httpClient
  41. * custom implementation of HTTP client to use; defaults to {@link HttpClient}.
  42. * @property {?HttpResponseHandler} httpResponseHandler
  43. * custom implementation of HTTP response handler; defaults to {@link HttpResponseHandler}
  44. * @property {?firebase.database.Database} firebaseDatabase
  45. * the Firebase Database that will be used to retrieve data from
  46. * @property {?ActorProvider} actorProvider
  47. * the provider of the user interacting with Spine
  48. * @property {?Client} implementation
  49. * the custom implementation of `Client`
  50. * @property {?Routing} routing
  51. * custom configuration of HTTP endpoints
  52. * @property {?TenantProvider} tenantProvider
  53. * the provider of an active tenant ID, if not specified, the application is considered
  54. * single-tenant
  55. * @property {?Duration} subscriptionKeepUpInterval
  56. * the custom interval for sending requests to keep up subscriptions
  57. */
  58. /**
  59. * An abstract factory for creation of `Client` instances.
  60. *
  61. * Ensures that the `ClientOptions` contain list of the `index.js` files generated by
  62. * {@link https://github.com/SpineEventEngine/base/tree/master/tools/proto-js-plugin the Protobuf plugin for JS}
  63. * and performs registration of types and parsers containing in these files.
  64. *
  65. * Creation of the concrete implementation of `Client` instances is delegated to inheritors.
  66. */
  67. export class AbstractClientFactory {
  68. /**
  69. * Creates a new instance of `Client` implementation in accordance with given options.
  70. *
  71. * @param {!ClientOptions} options client initialization options
  72. * @return {Client} a `Client` instance
  73. */
  74. static createClient(options) {
  75. this._ensureOptionsSufficient(options);
  76. return this._clientFor(options);
  77. }
  78. /**
  79. * Creates a new instance of `Client` implementation in accordance with given options.
  80. *
  81. * @param {!ClientOptions} options
  82. * @return {Client}
  83. * @protected
  84. */
  85. static _clientFor(options) {
  86. throw new Error('Not implemented in abstract base')
  87. }
  88. /**
  89. * Creates a new instance of `QueryingClient` implementation in accordance with given options.
  90. *
  91. * @param {!ClientOptions} options client initialization options
  92. * @return {QueryingClient} a `QueryingClient` instance
  93. */
  94. static createQuerying(options) {
  95. throw new Error('Not implemented in abstract base')
  96. }
  97. /**
  98. * Creates a new instance of `SubscribingClient` implementation in accordance with given options.
  99. *
  100. * @param {!ClientOptions} options client initialization options
  101. * @return {SubscribingClient} a `SubscribingClient` instance
  102. */
  103. static createSubscribing(options) {
  104. throw new Error('Not implemented in abstract base')
  105. }
  106. /**
  107. * Creates a new instance of `CommandingClient` implementation in accordance with given options.
  108. *
  109. * @param {!ClientOptions} options client initialization options
  110. * @return {CommandingClient} a `CommandingClient` instance
  111. */
  112. static createCommanding(options) {
  113. const httpClient = this._createHttpClient(options);
  114. const httpResponseHandler = this._createHttpResponseHandler(options)
  115. const endpoint = new HttpEndpoint(httpClient, httpResponseHandler, options.routing);
  116. const requestFactory = ActorRequestFactory.create(options);
  117. return new CommandingClient(endpoint, requestFactory);
  118. }
  119. /**
  120. * Creates an HTTP client basing on the passed {@link ClientOptions}.
  121. *
  122. * In case a custom HTTP client is specified via the `options`, this instance is returned.
  123. * Otherwise, a new instance of `HttpClient` is returned.
  124. *
  125. * @param {!ClientOptions} options client initialization options
  126. * @return {HttpClient} an instance of HTTP client
  127. * @protected
  128. */
  129. static _createHttpClient(options) {
  130. const customClient = options.httpClient;
  131. if (!!customClient) {
  132. if (!customClient instanceof HttpClient) {
  133. throw new Error('The custom HTTP client implementation passed via `options.httpClient` ' +
  134. 'must extend `HttpClient`.');
  135. }
  136. return customClient;
  137. } else {
  138. return new HttpClient(options.endpointUrl);
  139. }
  140. }
  141. /**
  142. * Creates an HTTP response handler judging on the passed {@link ClientOptions}.
  143. *
  144. * In case a custom HTTP response handler is specified via the `options`,
  145. * this instance is returned. Otherwise, a new instance of `HttpResponseHandler` is returned.
  146. *
  147. * @param {!ClientOptions} options client initialization options
  148. * @return {HttpResponseHandler} an instance of HTTP response handler
  149. * @protected
  150. */
  151. static _createHttpResponseHandler(options) {
  152. const customHandler = options.httpResponseHandler;
  153. if (!!customHandler) {
  154. if (!customHandler instanceof HttpResponseHandler) {
  155. throw new Error('The custom HTTP response handler implementation' +
  156. ' passed via `options.httpResponseHandler` must extend `HttpResponseHandler`.');
  157. }
  158. return customHandler;
  159. } else {
  160. return new HttpResponseHandler();
  161. }
  162. }
  163. /**
  164. * Ensures whether options object is sufficient for client initialization.
  165. *
  166. * @param {!ClientOptions} options
  167. * @protected
  168. */
  169. static _ensureOptionsSufficient(options) {
  170. if (!options) {
  171. throw new Error('Unable to initialize client. The `ClientOptions` is undefined.');
  172. }
  173. const indexFiles = options.protoIndexFiles;
  174. if (!Array.isArray(indexFiles) || indexFiles.length === 0) {
  175. throw new Error('Only a non-empty array is allowed as ClientOptions.protoIndexFiles parameter.');
  176. }
  177. indexFiles.forEach(indexFile => {
  178. if (typeof indexFile !== 'object'
  179. || !(indexFile.types instanceof Map)
  180. || !(indexFile.parsers instanceof Map) ) {
  181. throw new Error('Unable to register Protobuf index files.' +
  182. ' Check the `ClientOptions.protoIndexFiles` contains files' +
  183. ' generated with "io.spine.tools:spine-proto-js-plugin".');
  184. }
  185. });
  186. }
  187. }
  188. /**
  189. * An implementation of the `AbstractClientFactory` that returns a client instance
  190. * provided in `ClientOptions` parameter.
  191. */
  192. export class CustomClientFactory extends AbstractClientFactory {
  193. /**
  194. * Returns a custom `Client` implementation provided in options. Expects that the given options
  195. * contain an implementation which extends `Client`.
  196. *
  197. * Can be used to provide mock implementations of `Client`.
  198. *
  199. * @param {ClientOptions} options
  200. * @return {Client} a custom `Client` implementation provided in options
  201. * @override
  202. */
  203. static _clientFor(options) {
  204. return options.implementation;
  205. }
  206. /**
  207. * Returns a custom `QueryingClient` implementation provided in options. Expects that the given
  208. * options contain an implementation which extends `QueryingClient`.
  209. *
  210. * Can be used to provide mock implementations of `QueryingClient`.
  211. *
  212. * @param {ClientOptions} options
  213. * @return {QueryingClient} a custom `QueryingClient` implementation provided in options
  214. * @override
  215. */
  216. static createQuerying(options) {
  217. return options.implementation;
  218. }
  219. /**
  220. * Returns a custom `SubscribingClient` implementation provided in options. Expects that the given
  221. * options contain an implementation which extends `SubscribingClient`.
  222. *
  223. * Can be used to provide mock implementations of `SubscribingClient`.
  224. *
  225. * @param {ClientOptions} options
  226. * @return {SubscribingClient} a custom `SubscribingClient` implementation provided in options
  227. * @override
  228. */
  229. static createSubscribing(options) {
  230. return options.implementation;
  231. }
  232. /**
  233. * Returns a custom `CommandingClient` implementation provided in options. Expects that the given
  234. * options contain an implementation which extends `CommandingClient`.
  235. *
  236. * Can be used to provide mock implementations of `CommandingClient`.
  237. *
  238. * @param {ClientOptions} options
  239. * @return {CommandingClient} a custom `CommandingClient` implementation provided in options
  240. * @override
  241. */
  242. static createCommanding(options) {
  243. return options.implementation;
  244. }
  245. /**
  246. * @override
  247. */
  248. static _ensureOptionsSufficient(options) {
  249. super._ensureOptionsSufficient(options);
  250. const customClient = options.implementation;
  251. if (!customClient || !(customClient instanceof Client)) {
  252. throw new Error('Unable to initialize custom client implementation.' +
  253. ' The `ClientOptions.implementation` must extend `Client`.');
  254. }
  255. }
  256. }