diff --git a/client-js/main/client/client-factory.js b/client-js/main/client/client-factory.js index 3cef1da42..bc247c741 100644 --- a/client-js/main/client/client-factory.js +++ b/client-js/main/client/client-factory.js @@ -31,6 +31,7 @@ import {Client} from './client'; import {CommandingClient} from "./commanding-client"; import {HttpClient} from "./http-client"; import {HttpEndpoint} from "./http-endpoint"; +import {HttpResponseHandler} from "./http-response-handler"; /** * @typedef {Object} ClientOptions a type of object for initialization of Spine client @@ -39,6 +40,10 @@ import {HttpEndpoint} from "./http-endpoint"; * 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} * @property {?string} endpointUrl * the URL of the Spine-based backend endpoint + * @property {?HttpClient} httpClient + * custom implementation of HTTP client to use; defaults to {@link HttpClient}. + * @property {?HttpResponseHandler} httpResponseHandler + * custom implementation of HTTP response handler; defaults to {@link HttpResponseHandler} * @property {?firebase.database.Database} firebaseDatabase * the Firebase Database that will be used to retrieve data from * @property {?ActorProvider} actorProvider @@ -114,13 +119,60 @@ export class AbstractClientFactory { * @return {CommandingClient} a `CommandingClient` instance */ static createCommanding(options) { - const httpClient = new HttpClient(options.endpointUrl); - const endpoint = new HttpEndpoint(httpClient, options.routing); + const httpClient = this._createHttpClient(options); + const httpResponseHandler = this._createHttpResponseHandler(options) + const endpoint = new HttpEndpoint(httpClient, httpResponseHandler, options.routing); const requestFactory = ActorRequestFactory.create(options); return new CommandingClient(endpoint, requestFactory); } + /** + * Creates an HTTP client basing on the passed {@link ClientOptions}. + * + * In case a custom HTTP client is specified via the `options`, this instance is returned. + * Otherwise, a new instance of `HttpClient` is returned. + * + * @param {!ClientOptions} options client initialization options + * @return {HttpClient} an instance of HTTP client + * @protected + */ + static _createHttpClient(options) { + const customClient = options.httpClient; + if (!!customClient) { + if (!customClient instanceof HttpClient) { + throw new Error('The custom HTTP client implementation passed via `options.httpClient` ' + + 'must extend `HttpClient`.'); + } + return customClient; + } else { + return new HttpClient(options.endpointUrl); + } + } + + /** + * Creates an HTTP response handler judging on the passed {@link ClientOptions}. + * + * In case a custom HTTP response handler is specified via the `options`, + * this instance is returned. Otherwise, a new instance of `HttpResponseHandler` is returned. + * + * @param {!ClientOptions} options client initialization options + * @return {HttpResponseHandler} an instance of HTTP response handler + * @protected + */ + static _createHttpResponseHandler(options) { + const customHandler = options.httpResponseHandler; + if (!!customHandler) { + if (!customHandler instanceof HttpResponseHandler) { + throw new Error('The custom HTTP response handler implementation' + + ' passed via `options.httpResponseHandler` must extend `HttpResponseHandler`.'); + } + return customHandler; + } else { + return new HttpResponseHandler(); + } + } + /** * Ensures whether options object is sufficient for client initialization. * @@ -218,8 +270,8 @@ export class CustomClientFactory extends AbstractClientFactory { super._ensureOptionsSufficient(options); const customClient = options.implementation; if (!customClient || !(customClient instanceof Client)) { - throw new Error('Unable to initialize custom implementation.' + - ' The `ClientOptions.implementation` should extend Client.'); + throw new Error('Unable to initialize custom client implementation.' + + ' The `ClientOptions.implementation` must extend `Client`.'); } } } diff --git a/client-js/main/client/direct-client.js b/client-js/main/client/direct-client.js index db04fe494..992a372e9 100644 --- a/client-js/main/client/direct-client.js +++ b/client-js/main/client/direct-client.js @@ -51,8 +51,9 @@ import TypeParsers from "./parser/type-parsers"; export class DirectClientFactory extends AbstractClientFactory { static _clientFor(options) { - const httpClient = new HttpClient(options.endpointUrl); - const endpoint = new HttpEndpoint(httpClient, options.routing); + const httpClient = this._createHttpClient(options); + const httpResponseHandler = this._createHttpResponseHandler(options); + const endpoint = new HttpEndpoint(httpClient, httpResponseHandler, options.routing); const requestFactory = ActorRequestFactory.create(options); const querying = new DirectQueryingClient(endpoint, requestFactory); @@ -62,10 +63,10 @@ export class DirectClientFactory extends AbstractClientFactory { } static createQuerying(options) { - const httpClient = new HttpClient(options.endpointUrl); - const endpoint = new HttpEndpoint(httpClient, options.routing); + const httpClient = this._createHttpClient(options); + const httpResponseHandler = this._createHttpResponseHandler(options); + const endpoint = new HttpEndpoint(httpClient, httpResponseHandler, options.routing); const requestFactory = ActorRequestFactory.create(options); - return new DirectQueryingClient(endpoint, requestFactory); } diff --git a/client-js/main/client/firebase-client.js b/client-js/main/client/firebase-client.js index d5358816b..6a61d0e7c 100644 --- a/client-js/main/client/firebase-client.js +++ b/client-js/main/client/firebase-client.js @@ -138,15 +138,11 @@ class EventSubscription extends SpineSubscription { class FirebaseQueryingClient extends QueryingClient { /** - * A protected constructor for customization. - * - * Use `FirebaseClient#usingFirebase()` for instantiation + * Creates an instance of the client. * * @param {!HttpEndpoint} endpoint the server endpoint to execute queries and commands * @param {!FirebaseDatabaseClient} firebaseDatabase the client to read the query results from * @param {!ActorRequestFactory} actorRequestFactory a factory to instantiate the actor requests with - * - * @protected */ constructor(endpoint, firebaseDatabase, actorRequestFactory) { super(actorRequestFactory); @@ -176,9 +172,7 @@ const EVENT_TYPE_URL = 'type.spine.io/spine.core.Event'; class FirebaseSubscribingClient extends SubscribingClient { /** - * A protected constructor for customization. - * - * Use `FirebaseClient#usingFirebase()` for instantiation. + * Creates an instance of the client. * * @param {!HttpEndpoint} endpoint * the server endpoint to execute queries and commands @@ -188,8 +182,6 @@ class FirebaseSubscribingClient extends SubscribingClient { * a factory to instantiate the actor requests with * @param {!FirebaseSubscriptionService} subscriptionService * a service handling the subscriptions - * - * @protected */ constructor(endpoint, firebaseDatabase, actorRequestFactory, subscriptionService) { super(actorRequestFactory); @@ -328,8 +320,9 @@ export class FirebaseClientFactory extends AbstractClientFactory { * @override */ static _clientFor(options) { - const httpClient = new HttpClient(options.endpointUrl); - const endpoint = new HttpEndpoint(httpClient, options.routing); + const httpClient = this._createHttpClient(options); + const httpResponseHandler = this._createHttpResponseHandler(options); + const endpoint = new HttpEndpoint(httpClient, httpResponseHandler, options.routing); const firebaseDatabaseClient = new FirebaseDatabaseClient(options.firebaseDatabase); const requestFactory = ActorRequestFactory.create(options); const subscriptionService = @@ -345,8 +338,9 @@ export class FirebaseClientFactory extends AbstractClientFactory { } static createQuerying(options) { - const httpClient = new HttpClient(options.endpointUrl); - const endpoint = new HttpEndpoint(httpClient, options.routing); + const httpClient = this._createHttpClient(options); + const httpResponseHandler = this._createHttpResponseHandler(options); + const endpoint = new HttpEndpoint(httpClient, httpResponseHandler, options.routing); const firebaseDatabaseClient = new FirebaseDatabaseClient(options.firebaseDatabase); const requestFactory = ActorRequestFactory.create(options); @@ -354,8 +348,9 @@ export class FirebaseClientFactory extends AbstractClientFactory { } static createSubscribing(options) { - const httpClient = new HttpClient(options.endpointUrl); - const endpoint = new HttpEndpoint(httpClient, options.routing); + const httpClient = this._createHttpClient(options); + const httpResponseHandler = this._createHttpResponseHandler(options); + const endpoint = new HttpEndpoint(httpClient, httpResponseHandler, options.routing); const firebaseDatabaseClient = new FirebaseDatabaseClient(options.firebaseDatabase); const requestFactory = ActorRequestFactory.create(options); const subscriptionService = diff --git a/client-js/main/client/http-client.js b/client-js/main/client/http-client.js index 1c707876b..59e8da73e 100644 --- a/client-js/main/client/http-client.js +++ b/client-js/main/client/http-client.js @@ -41,35 +41,74 @@ export class HttpClient { /** * Creates a new instance of HttpClient. * - * @param {!string} appBaseUrl an application base URL (the protocol and the domain name) represented as - * a string + * @param {!string} appBaseUrl an application base URL (the protocol and the domain name) + * represented as a string */ constructor(appBaseUrl) { this._appBaseUrl = appBaseUrl; } /** - * Sends the given message to the given endpoint. + * Sends the given message to the given endpoint via `POST` request. * * The message is sent as in form of a Base64-encoded byte string. * * @param {!string} endpoint a endpoint to send the message to * @param {!TypedMessage} message a message to send, as a {@link TypedMessage} - * @return {Promise} a message sending promise to be fulfilled with a response, or rejected if - * an error occurs + * @return {Promise} a message sending promise to be fulfilled with a response, + * or rejected if an error occurs + * @see toBody + * @see headers */ postMessage(endpoint, message) { - const messageString = message.toBase64(); + const messageString = this.toBody(message); const path = endpoint.startsWith('/') ? endpoint : '/' + endpoint; const url = this._appBaseUrl + path; const request = { method: 'POST', body: messageString, - headers: { - 'Content-Type': 'application/x-protobuf' - }, - mode: 'cors' + headers: this.headers(message), + mode: this.requestMode(message) }; return fetch(url, request); } + + /** + * Returns the mode in which the HTTP request transferring the given message is sent. + * + * This implementation returns `cors`. + * + * @param {!TypedMessage} message a message to send, as a {@link TypedMessage} + * @return {string} the mode of HTTP requests to use + */ + requestMode(message) { + return 'cors'; + } + + /** + * Returns the string-typed map of HTTP header names to header values, + * which to use in order to send the passed message. + * + * In this implementation, returns {'Content-Type': 'application/x-protobuf'}. + * + * @param {!TypedMessage} message a message to send, as a {@link TypedMessage} + * @returns {{"Content-Type": string}} + */ + headers(message) { + return { + 'Content-Type': 'application/x-protobuf' + }; + } + + /** + * Transforms the given message to a string, which would become a POST request body. + * + * Uses {@link TypedMessage#toBase64 Base64 encoding} to transform the message. + * + * @param {!TypedMessage} message a message to transform into a POST body + * @returns {!string} transformed message + */ + toBody(message) { + return message.toBase64(); + } } diff --git a/client-js/main/client/http-endpoint.js b/client-js/main/client/http-endpoint.js index 817024a8e..6c198cd9f 100644 --- a/client-js/main/client/http-endpoint.js +++ b/client-js/main/client/http-endpoint.js @@ -27,8 +27,8 @@ "use strict"; import {TypedMessage} from './typed-message'; -import {ClientError, ConnectionError, ServerError, SpineError} from './errors'; import {Subscriptions} from '../proto/spine/web/keeping_up_pb'; +import {HttpResponseHandler} from "./http-response-handler"; /** * @typedef {Object} SubscriptionRouting @@ -232,12 +232,14 @@ 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, routing) { + constructor(httpClient, responseHandler, routing) { super(); this._httpClient = httpClient; this._routing = routing; + this._responseHandler = responseHandler; } /** @@ -365,85 +367,11 @@ export class HttpEndpoint extends Endpoint { return new Promise((resolve, reject) => { this._httpClient .postMessage(endpoint, message) - .then(HttpEndpoint._jsonOrError, HttpEndpoint._connectionError) + .then(this._responseHandler.handle + .bind(this._responseHandler), + this._responseHandler.onConnectionError + .bind(this._responseHandler)) .then(resolve, reject); }); } - - /** - * Retrieves the JSON data from the given response if it was successful, rejects - * with a respective error otherwise. - * - * @param {!Response} response an HTTP request response - * @return {Promise} a promise of a successful server response JSON data, - * rejected if the client response is not `2xx`, - * or if JSON parsing fails - * @private - */ - static _jsonOrError(response) { - const statusCode = response.status; - if (HttpEndpoint._isSuccessfulResponse(statusCode)) { - return HttpEndpoint._parseJson(response); - } - else if (HttpEndpoint._isClientErrorResponse(statusCode)) { - return Promise.reject(new ClientError(response.statusText, response)); - } - else if(HttpEndpoint._isServerErrorResponse(statusCode)) { - return Promise.reject(new ServerError(response)); - } - } - - /** - * Parses the given response JSON data, rejects if parsing fails. - * - * @param {!Response} response an HTTP request response - * @return {Promise} a promise of a server response parsing to be fulfilled - * with a JSON data or rejected with {@link SpineError} if - * JSON parsing fails. - * @private - */ - static _parseJson(response) { - return response.json() - .then(json => Promise.resolve(json)) - .catch(error => Promise.reject(new SpineError('Failed to parse response JSON', error))); - } - - /** - * Gets the error caught from the {@link HttpClient#postMessage} and returns - * a rejected promise with a given error wrapped into {@link ConnectionError}. - * - * @param {!Error} error an error which occurred upon message sending - * @return {Promise} a rejected promise with a `ConnectionError` - * @private - */ - static _connectionError(error) { - return Promise.reject(new ConnectionError(error)); - } - - /** - * @param {!number} statusCode an HTTP request response status code - * @return {boolean} `true` if the response status code is from 200 to 299, `false` otherwise - * @private - */ - static _isSuccessfulResponse(statusCode) { - return 200 <= statusCode && statusCode < 300; - } - - /** - * @param {!number} statusCode an HTTP request response status code - * @return {boolean} `true` if the response status code is from 400 to 499, `false` otherwise - * @private - */ - static _isClientErrorResponse(statusCode) { - return 400 <= statusCode && statusCode < 500; - } - - /** - * @param {!number} statusCode an HTTP request response status code - * @return {boolean} `true` if the response status code is from 500, `false` otherwise - * @private - */ - static _isServerErrorResponse(statusCode) { - return 500 <= statusCode; - } } diff --git a/client-js/main/client/http-response-handler.js b/client-js/main/client/http-response-handler.js new file mode 100644 index 000000000..93d7263ac --- /dev/null +++ b/client-js/main/client/http-response-handler.js @@ -0,0 +1,131 @@ +/* + * 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 {ClientError, ConnectionError, ServerError, SpineError} from "./errors"; + +/** + * Receives the HTTP response, and turns it into a JS object. + * + * Handles the response failures as well, in which case a corresponding error object + * is returned via a `Promise`. + * + * Only `2xx` response codes count as successful. All other response + * codes are considered erroneous. + * + * By default, expects the input to be a JSON string. Users may choose to customize the behavior + * by extending this type, and supplying the custom implementation via {@link ClientOptions}. + */ +export class HttpResponseHandler { + + /** + * Retrieves the JS object by transforming the contents + * of the given HTTP response if it was successful, + * rejects with a respective error otherwise. + * + * @param {!Response} response an HTTP request response + * @return {Promise} a promise of a successful server response data, + * rejected if the client response is not `2xx`, + * or if the transformation-to-object fails + * for the response contents. + * @see parse + */ + handle(response) { + const statusCode = response.status; + if (HttpResponseHandler._isSuccessfulResponse(statusCode)) { + return this.parse(response); + } else if (HttpResponseHandler._isClientErrorResponse(statusCode)) { + return Promise.reject(new ClientError(response.statusText, response)); + } else if (HttpResponseHandler._isServerErrorResponse(statusCode)) { + return Promise.reject(new ServerError(response)); + } + } + + /** + * Transforms the response into JS object by parsing the response contents. + * + * This implementation expects the response to contain JSON data. + * + * @param response an HTTP response + * @return {Promise} a promise of JS object, + * or a rejection with the corresponding `SpineError` + * @protected + */ + parse(response) { + return response.json() + .then(json => Promise.resolve(json)) + .catch(error => + Promise.reject(new SpineError('Failed to parse response JSON', error)) + ); + } + + /** + * Obtains the error caught from and erroneous HTTP request, and returns + * a rejected promise with a given error wrapped into {@link ConnectionError}. + * + * This handling method differs from others, since it is designed to handle the issues + * which were caused by an inability to send the HTTP request itself — so in this case + * there is no HTTP response. Note, that {@link handle} is designed + * to process the HTTP response, including erroneous responses. + * + * @param {!Error} error an error which occurred upon sending an HTTP request + * @return {Promise} a rejected promise with a `ConnectionError` + */ + onConnectionError(error) { + return Promise.reject(new ConnectionError(error)); + } + + /** + * @param {!number} statusCode an HTTP request response status code + * @return {boolean} `true` if the response status code is from 200 to 299, + * `false` otherwise + * @protected + */ + static _isSuccessfulResponse(statusCode) { + return 200 <= statusCode && statusCode < 300; + } + + /** + * @param {!number} statusCode an HTTP request response status code + * @return {boolean} `true` if the response status code is from 400 to 499, + * `false` otherwise + * @protected + */ + static _isClientErrorResponse(statusCode) { + return 400 <= statusCode && statusCode < 500; + } + + /** + * @param {!number} statusCode an HTTP request response status code + * @return {boolean} `true` if the response status code is from 500, + * `false` otherwise + * @protected + */ + static _isServerErrorResponse(statusCode) { + return 500 <= statusCode; + } +} \ No newline at end of file diff --git a/client-js/package.json b/client-js/package.json index 6ac5491cb..8d5ff85f8 100644 --- a/client-js/package.json +++ b/client-js/package.json @@ -1,6 +1,6 @@ { "name": "spine-web", - "version": "1.9.0-SNAPSHOT.8", + "version": "1.9.0-SNAPSHOT.9", "license": "Apache-2.0", "description": "A JS client for interacting with Spine applications.", "homepage": "https://spine.io", diff --git a/client-js/test/client/client-factory-test.js b/client-js/test/client/client-factory-test.js index 5ae4b46e9..be54a133a 100644 --- a/client-js/test/client/client-factory-test.js +++ b/client-js/test/client/client-factory-test.js @@ -32,9 +32,23 @@ import {ActorProvider} from '@lib/client/actor-request-factory'; import {init} from '@lib/client/spine'; import {Client} from "@lib/client/client"; import {CompositeClient} from "@lib/client/composite-client"; +import {HttpClient} from "../../main/client/http-client"; +import {HttpResponseHandler} from "../../main/client/http-response-handler"; class TestClient extends Client {} +class QueryHttpClient extends HttpClient {} + +class SubscriptionHttpClient extends HttpClient {} + +class CommandHttpClient extends HttpClient {} + +class QueryResponseHandler extends HttpResponseHandler {} + +class SubscriptionResponseHandler extends HttpResponseHandler {} + +class CommandResponseHandler extends HttpResponseHandler {} + describe('Client factory should', () => { it('create composite client', done => { @@ -62,4 +76,70 @@ describe('Client factory should', () => { assert.ok(client._commanding instanceof TestClient); done(); }); + + it('allow to customize `HttpClient`', done => { + const endpoint = 'example.com'; + const userId = new UserId(); + userId.value = 'me'; + const queryEndpoint = `${endpoint}/q/`; + const subscriptionEndpoint = `${endpoint}/s/`; + const commandEndpoint = `${endpoint}/c/`; + const client = init({ + protoIndexFiles: [types, testTypes], + forQueries: { + endpointUrl: queryEndpoint, + actorProvider: new ActorProvider(userId), + httpClient: new QueryHttpClient(queryEndpoint) + }, + forSubscriptions: { + endpointUrl: subscriptionEndpoint, + actorProvider: new ActorProvider(userId), + firebaseDatabase: "mock database", + httpClient: new SubscriptionHttpClient(subscriptionEndpoint) + }, + forCommands: { + endpointUrl: commandEndpoint, + actorProvider: new ActorProvider(userId), + httpClient: new CommandHttpClient(commandEndpoint) + } + }); + assert.ok(client._commanding._endpoint._httpClient instanceof CommandHttpClient); + assert.strictEqual(client._commanding._endpoint._httpClient._appBaseUrl, commandEndpoint,); + + assert.ok(client._subscribing._endpoint._httpClient instanceof SubscriptionHttpClient); + assert.strictEqual(client._subscribing._endpoint._httpClient._appBaseUrl, subscriptionEndpoint); + + assert.ok(client._querying._endpoint._httpClient instanceof QueryHttpClient); + assert.strictEqual(client._querying._endpoint._httpClient._appBaseUrl, queryEndpoint); + done(); + }) + + it('allow to customize `HttpResponseHandler`', done => { + const endpoint = 'example.com'; + const userId = new UserId(); + userId.value = 'me'; + const client = init({ + protoIndexFiles: [types, testTypes], + forQueries: { + endpointUrl: endpoint, + actorProvider: new ActorProvider(userId), + httpResponseHandler: new QueryResponseHandler() + }, + forSubscriptions: { + endpointUrl: endpoint, + actorProvider: new ActorProvider(userId), + firebaseDatabase: "mock database", + httpResponseHandler: new SubscriptionResponseHandler() + }, + forCommands: { + endpointUrl: endpoint, + actorProvider: new ActorProvider(userId), + httpResponseHandler: new CommandResponseHandler() + } + }); + assert.ok(client._commanding._endpoint._responseHandler instanceof CommandResponseHandler); + assert.ok(client._subscribing._endpoint._responseHandler instanceof SubscriptionResponseHandler); + assert.ok(client._querying._endpoint._responseHandler instanceof QueryResponseHandler); + done(); + }) }); diff --git a/client-js/test/client/http-endpoint-test.js b/client-js/test/client/http-endpoint-test.js index 502eba982..87e497970 100644 --- a/client-js/test/client/http-endpoint-test.js +++ b/client-js/test/client/http-endpoint-test.js @@ -34,6 +34,7 @@ import {CreateTask} from '@testProto/spine/test/js/commands_pb'; import {ClientError, ConnectionError, ServerError, SpineError} from '@lib/client/errors'; import {Duration} from '@lib/client/time-utils'; import {fail} from './test-helpers'; +import {HttpResponseHandler} from "../../main/client/http-response-handler"; const MOCK_RESPONSE_STATUS_TEXT = 'Status text'; @@ -108,7 +109,7 @@ Given.HTTP_RESPONSE = { }; const httpClient = Given.httpClient(); -const httpEndpoint = new HttpEndpoint(httpClient); +const httpEndpoint = new HttpEndpoint(httpClient, new HttpResponseHandler()); describe('HttpEndpoint.command', function () { const timeoutDuration = new Duration({seconds: 5}); diff --git a/integration-tests/js-tests/package.json b/integration-tests/js-tests/package.json index 60b0b184a..8143d89ef 100644 --- a/integration-tests/js-tests/package.json +++ b/integration-tests/js-tests/package.json @@ -1,6 +1,6 @@ { "name": "client-js-tests", - "version": "1.9.0-SNAPSHOT.8", + "version": "1.9.0-SNAPSHOT.9", "license": "Apache-2.0", "description": "Tests of a `spine-web` JS library against the Spine-based application.", "scripts": { diff --git a/license-report.md b/license-report.md index 82e8e756a..324379a79 100644 --- a/license-report.md +++ b/license-report.md @@ -1,6 +1,6 @@ -# Dependencies of `io.spine:spine-client-js:1.9.0-SNAPSHOT.8` +# Dependencies of `io.spine:spine-client-js:1.9.0-SNAPSHOT.9` ## Runtime 1. **Group:** com.google.code.findbugs **Name:** jsr305 **Version:** 3.0.2 @@ -368,10 +368,10 @@ The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Wed Jan 04 15:35:39 WET 2023** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Thu Jan 12 13:42:53 WET 2023** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -#NPM dependencies of `spine-web@1.9.0-SNAPSHOT.8` +#NPM dependencies of `spine-web@1.9.0-SNAPSHOT.9` ## `Production` dependencies: @@ -405,7 +405,7 @@ This report was generated on **Wed Jan 04 15:35:39 WET 2023** using [Gradle-Lice 1. **rxjs@6.5.5** * Licenses: Apache-2.0 * Repository: [https://github.com/reactivex/rxjs](https://github.com/reactivex/rxjs) -1. **spine-web@1.9.0-SNAPSHOT.8** +1. **spine-web@1.9.0-SNAPSHOT.9** * Licenses: Apache-2.0 * Repository: [https://github.com/SpineEventEngine/web](https://github.com/SpineEventEngine/web) 1. **tr46@0.0.3** @@ -1958,7 +1958,7 @@ This report was generated on **Wed Jan 04 15:35:39 WET 2023** using [Gradle-Lice 1. **spdx-satisfies@4.0.1** * Licenses: MIT * Repository: [https://github.com/kemitchell/spdx-satisfies.js](https://github.com/kemitchell/spdx-satisfies.js) -1. **spine-web@1.9.0-SNAPSHOT.8** +1. **spine-web@1.9.0-SNAPSHOT.9** * Licenses: Apache-2.0 * Repository: [https://github.com/SpineEventEngine/web](https://github.com/SpineEventEngine/web) 1. **sprintf-js@1.0.3** @@ -2140,12 +2140,12 @@ This report was generated on **Wed Jan 04 15:35:39 WET 2023** using [Gradle-Lice * Repository: [https://github.com/sindresorhus/yocto-queue](https://github.com/sindresorhus/yocto-queue) -This report was generated on **Wed Jan 04 2023 15:35:40 GMT+0000 (Western European Standard Time)** using [NPM License Checker](https://github.com/davglass/license-checker) library. +This report was generated on **Thu Jan 12 2023 13:42:54 GMT+0000 (Western European Standard Time)** using [NPM License Checker](https://github.com/davglass/license-checker) library. -# Dependencies of `io.spine.gcloud:spine-firebase-web:1.9.0-SNAPSHOT.8` +# Dependencies of `io.spine.gcloud:spine-firebase-web:1.9.0-SNAPSHOT.9` ## Runtime 1. **Group:** com.fasterxml.jackson.core **Name:** jackson-annotations **Version:** 2.9.10 @@ -2932,12 +2932,12 @@ This report was generated on **Wed Jan 04 2023 15:35:40 GMT+0000 (Western Europe The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Wed Jan 04 15:35:45 WET 2023** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Thu Jan 12 13:42:59 WET 2023** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-js-tests:1.9.0-SNAPSHOT.8` +# Dependencies of `io.spine:spine-js-tests:1.9.0-SNAPSHOT.9` ## Runtime 1. **Group:** com.google.code.findbugs **Name:** jsr305 **Version:** 3.0.2 @@ -3327,12 +3327,12 @@ This report was generated on **Wed Jan 04 15:35:45 WET 2023** using [Gradle-Lice The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Wed Jan 04 15:35:50 WET 2023** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Thu Jan 12 13:43:04 WET 2023** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-test-app:1.9.0-SNAPSHOT.8` +# Dependencies of `io.spine:spine-test-app:1.9.0-SNAPSHOT.9` ## Runtime 1. **Group:** com.fasterxml.jackson.core **Name:** jackson-annotations **Version:** 2.9.10 @@ -4906,12 +4906,12 @@ This report was generated on **Wed Jan 04 15:35:50 WET 2023** using [Gradle-Lice The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Wed Jan 04 15:35:51 WET 2023** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Thu Jan 12 13:43:06 WET 2023** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-testutil-web:1.9.0-SNAPSHOT.8` +# Dependencies of `io.spine:spine-testutil-web:1.9.0-SNAPSHOT.9` ## Runtime 1. **Group:** com.google.android **Name:** annotations **Version:** 4.1.1.4 @@ -5370,12 +5370,12 @@ This report was generated on **Wed Jan 04 15:35:51 WET 2023** using [Gradle-Lice The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Wed Jan 04 15:35:53 WET 2023** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Thu Jan 12 13:43:08 WET 2023** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-web:1.9.0-SNAPSHOT.8` +# Dependencies of `io.spine:spine-web:1.9.0-SNAPSHOT.9` ## Runtime 1. **Group:** com.google.android **Name:** annotations **Version:** 4.1.1.4 @@ -5873,4 +5873,4 @@ This report was generated on **Wed Jan 04 15:35:53 WET 2023** using [Gradle-Lice The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Wed Jan 04 15:35:57 WET 2023** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). \ No newline at end of file +This report was generated on **Thu Jan 12 13:43:11 WET 2023** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). \ No newline at end of file diff --git a/pom.xml b/pom.xml index 6c8ed70ac..40a93f8e2 100644 --- a/pom.xml +++ b/pom.xml @@ -12,7 +12,7 @@ all modules and does not describe the project structure per-subproject. io.spine spine-web -1.9.0-SNAPSHOT.8 +1.9.0-SNAPSHOT.9 2015 diff --git a/version.gradle.kts b/version.gradle.kts index f85eeb1b3..b476a09a9 100644 --- a/version.gradle.kts +++ b/version.gradle.kts @@ -29,5 +29,5 @@ val spineTimeVersion: String by extra("1.9.0-SNAPSHOT.5") val spineCoreVersion: String by extra("1.9.0-SNAPSHOT.6") val spineVersion: String by extra(spineCoreVersion) -val versionToPublish: String by extra("1.9.0-SNAPSHOT.8") +val versionToPublish: String by extra("1.9.0-SNAPSHOT.9") val versionToPublishJs: String by extra(versionToPublish) diff --git a/web/src/main/java/io/spine/web/future/Completion.java b/web/src/main/java/io/spine/web/future/Completion.java index bc0a10c93..3a792729e 100644 --- a/web/src/main/java/io/spine/web/future/Completion.java +++ b/web/src/main/java/io/spine/web/future/Completion.java @@ -45,7 +45,7 @@ private Completion() { } /** - * Logs the exception of the given stage if any. + * Logs the exception raised at the given stage, if any. * *

Does nothing if the stage completes successfully. * @@ -53,7 +53,7 @@ private Completion() { * is complete. * * @param completionStage - * stage which may complete exceptionally + * stage which may complete with an exception */ public static void dispose(CompletionStage completionStage) { completionStage.whenComplete((result, exception) -> logException(exception)); diff --git a/web/src/main/java/io/spine/web/subscription/servlet/SubscriptionBulkKeepUpServlet.java b/web/src/main/java/io/spine/web/subscription/servlet/SubscriptionBulkKeepUpServlet.java index b45d74763..2a6f33ffa 100644 --- a/web/src/main/java/io/spine/web/subscription/servlet/SubscriptionBulkKeepUpServlet.java +++ b/web/src/main/java/io/spine/web/subscription/servlet/SubscriptionBulkKeepUpServlet.java @@ -41,7 +41,8 @@ * to process. After, a processing result is written to the servlet response. */ @SuppressWarnings("serial") // Java serialization is not supported. -public class SubscriptionBulkKeepUpServlet extends MessageServlet { +public abstract class SubscriptionBulkKeepUpServlet + extends MessageServlet { private final SubscriptionBridge bridge;