diff --git a/client-js/main/client/client.js b/client-js/main/client/client.js index b027346f..53506fde 100644 --- a/client-js/main/client/client.js +++ b/client-js/main/client/client.js @@ -183,6 +183,16 @@ export class Client { throw new Error('Not implemented in abstract base.'); } + /** + * Immediately cancels all active subscriptions. + * + * This endpoint is handy to use when an end-user chooses to end her session + * with the web app. E.g. all subscriptions should be cancelled upon user sign-out. + */ + cancelAllSubscriptions() { + throw new Error('Not implemented in abstract base.'); + } + /** * Subscribes to the given `Topic` instance. * diff --git a/client-js/main/client/composite-client.js b/client-js/main/client/composite-client.js index bb219155..8de05fb2 100644 --- a/client-js/main/client/composite-client.js +++ b/client-js/main/client/composite-client.js @@ -90,6 +90,13 @@ export class CompositeClient extends Client { return this._subscribing.subscribeTo(entityType, this); } + /** + * @override + */ + cancelAllSubscriptions() { + this._subscribing.cancelAllSubscriptions(); + } + /** * @override */ diff --git a/client-js/main/client/firebase-client.js b/client-js/main/client/firebase-client.js index 6a61d0e7..b1c0df0a 100644 --- a/client-js/main/client/firebase-client.js +++ b/client-js/main/client/firebase-client.js @@ -35,7 +35,6 @@ import {ActorRequestFactory} from './actor-request-factory'; import {AbstractClientFactory} from './client-factory'; import {CommandingClient} from "./commanding-client"; import {CompositeClient} from "./composite-client"; -import {HttpClient} from './http-client'; import {HttpEndpoint} from './http-endpoint'; import {FirebaseDatabaseClient} from './firebase-database-client'; import {FirebaseSubscriptionService} from './firebase-subscription-service'; @@ -84,9 +83,9 @@ class SpineSubscription extends Subscription { class EntitySubscription extends SpineSubscription { /** - * @param {Function} unsubscribe the callbacks that allows to cancel the subscription + * @param {Function} unsubscribe the callback that allows to cancel the subscription * @param {{itemAdded: Observable, itemChanged: Observable, itemRemoved: Observable}} observables - * the observables for entity changes + * the observables for entity change * @param {SubscriptionObject} subscription the wrapped subscription object */ constructor({ @@ -102,7 +101,13 @@ class EntitySubscription extends SpineSubscription { * @return {EntitySubscriptionObject} a plain object with observables and unsubscribe method */ toObject() { - return Object.assign({}, this._observables, {unsubscribe: () => this.unsubscribe()}); + return Object.assign({}, + this._observables, + { + unsubscribe: () => { + return this.unsubscribe(); + } + }); } } @@ -130,7 +135,13 @@ class EventSubscription extends SpineSubscription { */ toObject() { return Object.assign( - {}, {eventEmitted: this._observable}, {unsubscribe: () => this.unsubscribe()} + {}, + {eventEmitted: this._observable}, + { + unsubscribe: () => { + return this.unsubscribe(); + } + } ); } } @@ -243,6 +254,7 @@ class FirebaseSubscribingClient extends SubscribingClient { return new EntitySubscription({ unsubscribedBy: () => { FirebaseSubscribingClient._unsubscribe(pathSubscriptions); + this._subscriptionService.cancelSubscription(subscription); }, withObservables: { itemAdded: ObjectToProto.map(itemAdded.asObservable(), typeUrl), @@ -263,12 +275,20 @@ class FirebaseSubscribingClient extends SubscribingClient { return new EventSubscription({ unsubscribedBy: () => { FirebaseSubscribingClient._unsubscribe([pathSubscription]); + this._subscriptionService.cancelSubscription(subscription); }, withObservable: ObjectToProto.map(itemAdded.asObservable(), EVENT_TYPE_URL), forInternal: subscription }); } + /** + * @override + */ + cancelAllSubscriptions() { + this._subscriptionService.cancelAllSubscriptions(); + } + /** * Unsubscribes the provided Firebase subscriptions. * diff --git a/client-js/main/client/firebase-subscription-service.js b/client-js/main/client/firebase-subscription-service.js index 44b80c66..19ea4d89 100644 --- a/client-js/main/client/firebase-subscription-service.js +++ b/client-js/main/client/firebase-subscription-service.js @@ -38,8 +38,8 @@ import {Status} from '../proto/spine/core/response_pb'; const DEFAULT_KEEP_UP_INTERVAL = new Duration({minutes: 2}); /** - * A service that manages the active subscriptions periodically sending requests to keep them - * running. + * A service that manages the active subscriptions periodically sending requests + * to keep them running. */ export class FirebaseSubscriptionService { @@ -84,6 +84,33 @@ export class FirebaseSubscriptionService { } } + /** + * Immediately cancels all active subscriptions previously created through this service. + */ + cancelAllSubscriptions() { + const activeSubscriptions = this._subscriptions.filter(s => !s.closed); + if (activeSubscriptions.length > 0) { + const subscriptionMessages = activeSubscriptions.map(s => s.internal()) + this._endpoint.cancelAll(subscriptionMessages); + activeSubscriptions.forEach(s => { + s.unsubscribe() /* Calling RxJS's `unsubscribe` to stop propagating the updates. */ + this._removeSubscription(s) + }) + } + } + + /** + * Immediately cancels the given subscription, including cancelling it on the server-side. + * + * @param {SubscriptionObject} subscription the subscription to cancel + */ + cancelSubscription(subscription) { + this._endpoint.cancelSubscription(subscription) + .then(() => { + this._removeSubscription(subscription) + }); + } + /** * Indicates whether this service is running keeping up subscriptions. * @@ -150,10 +177,22 @@ export class FirebaseSubscriptionService { * Removes the provided subscription from subscriptions list, which stops any attempts * to update it. In case no more subscriptions are left, stops this service. * + * In case the passed subscription is not known to this service, does nothing. + * + * @param subscription a subscription to cancel; + * this method accepts values of both `SpineSubscription` + * and Proto `Subscription` types, + * and operates based on the subscription ID * @private */ _removeSubscription(subscription) { - const index = this._subscriptions.indexOf(subscription); + let id; + if (typeof subscription.id === 'function') { + id = subscription.id(); + } else { + id = subscription.getId().getValue(); + } + const index = this._subscriptions.findIndex(item => item.id() === id); this._subscriptions.splice(index, 1); if (this._subscriptions.length === 0) { diff --git a/client-js/main/client/subscribing-client.js b/client-js/main/client/subscribing-client.js index 62d5856d..6ad02d71 100644 --- a/client-js/main/client/subscribing-client.js +++ b/client-js/main/client/subscribing-client.js @@ -85,6 +85,13 @@ export class SubscribingClient { throw new Error('Not implemented in abstract base.'); } + /** + * Cancels all subscriptions, which were created through this instance of subscribing client. + */ + cancelAllSubscriptions() { + throw new Error('Not implemented in abstract base.'); + } + /** * Returns a new topic factory instance which can be further used for the `Topic` creation. * @@ -123,4 +130,13 @@ export class NoOpSubscribingClient extends SubscribingClient { subscribeToEvents(topic) { throw new Error(SUBSCRIPTIONS_NOT_SUPPORTED); } + + /** + * Always throws an error. + * + * @override + */ + cancelAllSubscriptions() { + throw new Error(SUBSCRIPTIONS_NOT_SUPPORTED); + } } diff --git a/client-js/package.json b/client-js/package.json index 8d5ff85f..73446ec2 100644 --- a/client-js/package.json +++ b/client-js/package.json @@ -1,6 +1,6 @@ { "name": "spine-web", - "version": "1.9.0-SNAPSHOT.9", + "version": "1.9.0-SNAPSHOT.10", "license": "Apache-2.0", "description": "A JS client for interacting with Spine applications.", "homepage": "https://spine.io", diff --git a/firebase-web/src/main/java/io/spine/web/firebase/FirebaseCredentials.java b/firebase-web/src/main/java/io/spine/web/firebase/FirebaseCredentials.java index bbec849e..c00292a7 100644 --- a/firebase-web/src/main/java/io/spine/web/firebase/FirebaseCredentials.java +++ b/firebase-web/src/main/java/io/spine/web/firebase/FirebaseCredentials.java @@ -50,8 +50,7 @@ * *

See Firebase REST docs. */ -@SuppressWarnings("deprecation") -// Use deprecated `GoogleCredential` to retain backward compatibility. +@SuppressWarnings("deprecation" /*`GoogleCredential` is used to retain backward compatibility.*/) public final class FirebaseCredentials implements HttpRequestInitializer { private static final String AUTH_DATABASE = "https://www.googleapis.com/auth/firebase.database"; diff --git a/firebase-web/src/main/java/io/spine/web/firebase/subscription/FirebaseSubscriptionBridge.java b/firebase-web/src/main/java/io/spine/web/firebase/subscription/FirebaseSubscriptionBridge.java index 56eaac34..83f06c12 100644 --- a/firebase-web/src/main/java/io/spine/web/firebase/subscription/FirebaseSubscriptionBridge.java +++ b/firebase-web/src/main/java/io/spine/web/firebase/subscription/FirebaseSubscriptionBridge.java @@ -209,13 +209,18 @@ public Builder setSubscriptionLifeSpan(Duration subscriptionLifeSpan) { /** * Creates a new instance of {@code FirebaseQueryBridge}. * + *

Mandatory fields are {@link #setFirebaseClient(FirebaseClient) firebaseClient} + * and {@link #setSubscriptionService(SubscriptionServiceImplBase) subscriptionService}. + * * @return new instance of {@code FirebaseQueryBridge} */ public FirebaseSubscriptionBridge build() { checkState(firebaseClient != null, - "Firebase database client is not set to FirebaseSubscriptionBridge."); + "Mandatory Firebase database client" + + " is not specified for `FirebaseSubscriptionBridge`."); checkState(subscriptionService != null, - "Subscription Service is not set to FirebaseSubscriptionBridge."); + "Mandatory Subscription Service is not specified" + + " for `FirebaseSubscriptionBridge`."); return new FirebaseSubscriptionBridge(this); } } diff --git a/firebase-web/src/main/java/io/spine/web/firebase/subscription/HealthLog.java b/firebase-web/src/main/java/io/spine/web/firebase/subscription/HealthLog.java index 458a33b8..b8d1129f 100644 --- a/firebase-web/src/main/java/io/spine/web/firebase/subscription/HealthLog.java +++ b/firebase-web/src/main/java/io/spine/web/firebase/subscription/HealthLog.java @@ -47,7 +47,7 @@ *

To understand whether a client is still listening to the {@code Topic} updates, she * periodically sends a {@link FirebaseSubscriptionBridge#keepUp(Subscription) keepUp(Subscription)} * request. The server records the timestamps of these requests in this log and counts the client - * alive, as long as the {@linkplain #withTimeout(Duration)} configured} timeout does not pass + * alive, as long as the {@linkplain #withTimeout(Duration) configured} timeout does not pass * since the last request. */ final class HealthLog { @@ -113,4 +113,15 @@ boolean isStale(Topic topic) { Duration elapsed = between(lastUpdate, now); return compare(elapsed, expirationTimeout) > 0; } + + /** + * Removes the given {@code Topic} from this health log. + * + *

In case this topic is not known to this registry, does nothing, allowing + * to safely clear the health log from stale topics potentially residing in storage + * on either client- or server-sides. + */ + void remove(Topic topic) { + updateTimes.remove(topic.getId()); + } } diff --git a/firebase-web/src/main/java/io/spine/web/firebase/subscription/SubscriptionRepository.java b/firebase-web/src/main/java/io/spine/web/firebase/subscription/SubscriptionRepository.java index 32eee4e9..48c1c471 100644 --- a/firebase-web/src/main/java/io/spine/web/firebase/subscription/SubscriptionRepository.java +++ b/firebase-web/src/main/java/io/spine/web/firebase/subscription/SubscriptionRepository.java @@ -34,7 +34,6 @@ import com.google.protobuf.Duration; import io.spine.client.Subscription; import io.spine.client.Topic; -import io.spine.json.Json; import io.spine.web.firebase.FirebaseClient; import io.spine.web.firebase.NodePath; import io.spine.web.firebase.NodePaths; @@ -44,6 +43,7 @@ import java.util.Optional; import static com.google.common.base.Preconditions.checkNotNull; +import static io.spine.json.Json.fromJson; import static io.spine.util.Exceptions.illegalStateWithCauseOf; import static io.spine.web.firebase.subscription.LazyRepository.lazy; @@ -81,7 +81,7 @@ final class SubscriptionRepository { /** * Fetches all the existing subscriptions from the Firebase and activates them. * - *

After calling this method, all the new subscriptions are automatically activates on this + *

After calling this method, all the new subscriptions are automatically activated on this * server instance. */ void subscribeToAll() { @@ -152,6 +152,7 @@ private void delete(Topic topic) { checkNotNull(topic); NodePath path = pathForSubscription(topic); firebase.delete(path); + healthLog.remove(topic); } private static NodePath pathForSubscription(Topic topic) { @@ -200,7 +201,7 @@ private static String asJson(DataSnapshot snapshot) { } private Topic loadTopic(String json) { - Topic topic = Json.fromJson(json, Topic.class); + Topic topic = fromJson(json, Topic.class); repository.healthLog.put(topic); return topic; } @@ -216,7 +217,31 @@ private void deleteOrActivate(Topic topic) { @Override public void onChildRemoved(DataSnapshot snapshot) { - // NOP. + String json = asJson(snapshot); + Optional topic = parseTopic(json); + topic.ifPresent(t -> { + HealthLog healthLog = repository.healthLog; + if (healthLog.isKnown(t)) { + repository.delete(t); + } + }); + } + + /** + * Safely parses the {@code Topic} from the passed JSON. + * + * @param json + * JSON to parse + * @return parsed {@code Topic} wrapped as {@code Optional}, + * or {@code Optional.empty()} if there was a parsing error + */ + private static Optional parseTopic(String json) { + try { + Topic topic = fromJson(json, Topic.class); + return Optional.of(topic); + } catch (RuntimeException e) { + return Optional.empty(); + } } @Override diff --git a/integration-tests/js-tests/package.json b/integration-tests/js-tests/package.json index 8143d89e..b43dd0c1 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.9", + "version": "1.9.0-SNAPSHOT.10", "license": "Apache-2.0", "description": "Tests of a `spine-web` JS library against the Spine-based application.", "scripts": { diff --git a/integration-tests/js-tests/test/firebase-client/subscribe-test.js b/integration-tests/js-tests/test/firebase-client/subscribe-test.js index eb566942..82f2fd20 100644 --- a/integration-tests/js-tests/test/firebase-client/subscribe-test.js +++ b/integration-tests/js-tests/test/firebase-client/subscribe-test.js @@ -221,6 +221,120 @@ describe('FirebaseClient subscription', function () { .catch(fail(done)); }); + /** + * This test creates two tasks, one after another, and then creates two subscriptions + * to changes of the created tasks. Prior to sending the renaming commands, + * the test code cancels all known subscriptions, and verifies that no `itemChanged` + * promises are resolved when the renaming commands are sent. + */ + it('may be cancelled along with all other subscriptions in a bulk', done => { + const prefix = 'spine-web-test-bulk-cancellation'; + const createFirst = TestEnvironment.createTaskCommand({ + withPrefix: prefix, + named: "first task" + }); + const createSecond = TestEnvironment.createTaskCommand({ + withPrefix: prefix, + named: "second task" + }); + + const firstId = createFirst.getId(); + const secondId = createSecond.getId(); + + function createFirstTask() { + client.command(createFirst) + .onOk(() => console.log(`1st task '${firstId.getValue()}' created.`)) + .onError(fail(done, 'Unexpected error while creating the 1st task.')) + .onImmediateRejection(fail(done, + 'Unexpected rejection while creating the 1st task.')) + .post(); + } + + function createSecondTask() { + client.command(createSecond) + .onOk(() => console.log(`2nd task '${secondId.getValue()}' created.`)) + .onError(fail(done, 'Unexpected error while creating the 2nd task.')) + .onImmediateRejection(fail(done, + 'Unexpected rejection while creating the 2nd task.')) + .post(); + } + + function renameFirstTask() { + const renameFirst = TestEnvironment.renameTaskCommand({ + withId: firstId.getValue(), + to: "first updated" + }); + + client.command(renameFirst) + .onOk(() => console.log(`1st task '${firstId.getValue()}' was renamed.`)) + .onError(fail(done, 'Unexpected error while renaming the 1st task.')) + .onImmediateRejection(fail(done, + 'Unexpected rejection while renaming the 1st task.')) + .post(); + } + + function renameSecondTask() { + const renameSecond = TestEnvironment.renameTaskCommand({ + withId: secondId.getValue(), + to: "second updated" + }); + client.command(renameSecond) + .onOk(() => console.log(`2nd task '${secondId.getValue()}' was renamed.`)) + .onError(fail(done, 'Unexpected error while renaming the 2nd task.')) + .onImmediateRejection(fail(done, + 'Unexpected rejection while renaming the 2nd task.')) + .post(); + } + + client.subscribeTo(Task) + .byId(firstId) + .post() + .then(({itemAdded, itemChanged, itemRemoved, unsubscribe}) => { + itemAdded.subscribe({ + next: () => { + createSecondTask(); + } + }); + itemChanged.subscribe({ + next: () => { fail(done, 'Unexpected 1st task change received, ' + + 'while the corresponding subscription had to be cancelled by now.'); } + }); + + client.subscribeTo(Task) + .byId(secondId) + .post() + .then(({itemAdded, itemChanged, itemRemoved, unsubscribe}) => { + itemChanged.subscribe({ + next: () => { fail(done, 'Unexpected 2nd task change received, ' + + 'while the corresponding subscription had to be cancelled by now.'); } + }); + itemAdded.subscribe({ + next: async () => { + client.cancelAllSubscriptions(); + renameFirstTask(); + renameSecondTask(); + + await oneHundredMs(); + + done(); + } + }); + }) + }) + .catch(fail(done)); + createFirstTask(); + }); + + /** + * Returns a promise to be resolved after 100 ms. + * + * @returns {Promise} + */ + function oneHundredMs() { + return new Promise(resolve => + setTimeout(() => resolve(), 100)) + } + it('is notified when the entity no longer matches the subscription criteria', done => { const initialTaskName = 'Initial task name'; const nameAfterRenamed = 'Renamed task'; @@ -425,15 +539,13 @@ describe('FirebaseClient subscription', function () { }); }); - it('and canceled on the next keep up interval if unsubscribed', done => { + it('and cancel subscription immediately if unsubscribed', done => { const cancelEndpoint = cancelEndpointSpy(); - subscribeToAllTasks().then(async ({itemAdded, itemChanged, itemRemoved, unsubscribe}) => { assert.ok(cancelEndpoint.notCalled); unsubscribe(); - await nextInterval(); assert.ok(cancelEndpoint.calledOnce); - const subscriptionMessage = cancelEndpoint.getCall(0).args[0][0]; + const subscriptionMessage = cancelEndpoint.getCall(0).args[0]; checkAllTasks(subscriptionMessage); done(); }); @@ -450,10 +562,10 @@ describe('FirebaseClient subscription', function () { assert.ok(keepUpEndpoint.calledOnce); assert.ok(cancelEndpoint.notCalled); unsubscribe(); + assert.ok(cancelEndpoint.calledOnce); await nextInterval(); assert.ok(keepUpEndpoint.calledOnce); - assert.ok(cancelEndpoint.calledOnce); await nextInterval(); assert.ok(keepUpEndpoint.calledOnce); @@ -504,7 +616,7 @@ describe('FirebaseClient subscription', function () { function cancelEndpointSpy() { const httpEndpoint = client._subscribing._endpoint; - return sandbox.spy(httpEndpoint, 'cancelAll'); + return sandbox.spy(httpEndpoint, 'cancelSubscription'); } /** diff --git a/integration-tests/js-tests/test/test-helpers.js b/integration-tests/js-tests/test/test-helpers.js index f92e9e2d..803a5bef 100644 --- a/integration-tests/js-tests/test/test-helpers.js +++ b/integration-tests/js-tests/test/test-helpers.js @@ -139,7 +139,7 @@ function arraysEqualDeep(arr1, arr2, compare) { * @param {EntitySubscriptionObject} subscription a subscription to retrieve values * @param {(o1: T, o2: T) => boolean} compare a function that compares objects of `T` type; * returns `true` if objects are considered equal, `false` otherwise - * @return {Observable} an observable that emits a list of values, composed from the given + * @return {Observable} an observable that emits a list of values, composed of the given * subscription object * * @template a class of a subscription target entities diff --git a/license-report.md b/license-report.md index 324379a7..ab2ceed5 100644 --- a/license-report.md +++ b/license-report.md @@ -1,6 +1,6 @@ -# Dependencies of `io.spine:spine-client-js:1.9.0-SNAPSHOT.9` +# Dependencies of `io.spine:spine-client-js:1.9.0-SNAPSHOT.10` ## 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 **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). +This report was generated on **Fri Jan 27 17:43:52 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.9` +#NPM dependencies of `spine-web@1.9.0-SNAPSHOT.10` ## `Production` dependencies: @@ -405,7 +405,7 @@ This report was generated on **Thu Jan 12 13:42:53 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.9** +1. **spine-web@1.9.0-SNAPSHOT.10** * 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 **Thu Jan 12 13:42:53 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.9** +1. **spine-web@1.9.0-SNAPSHOT.10** * 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 **Thu Jan 12 13:42:53 WET 2023** using [Gradle-Lice * Repository: [https://github.com/sindresorhus/yocto-queue](https://github.com/sindresorhus/yocto-queue) -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. +This report was generated on **Fri Jan 27 2023 17:43:53 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.9` +# Dependencies of `io.spine.gcloud:spine-firebase-web:1.9.0-SNAPSHOT.10` ## Runtime 1. **Group:** com.fasterxml.jackson.core **Name:** jackson-annotations **Version:** 2.9.10 @@ -2932,12 +2932,12 @@ This report was generated on **Thu Jan 12 2023 13:42:54 GMT+0000 (Western Europe The dependencies distributed under several licenses, are used according their commercial-use-friendly 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). +This report was generated on **Fri Jan 27 17:43: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.9` +# Dependencies of `io.spine:spine-js-tests:1.9.0-SNAPSHOT.10` ## Runtime 1. **Group:** com.google.code.findbugs **Name:** jsr305 **Version:** 3.0.2 @@ -3327,12 +3327,12 @@ This report was generated on **Thu Jan 12 13:42:59 WET 2023** using [Gradle-Lice The dependencies distributed under several licenses, are used according their commercial-use-friendly 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). +This report was generated on **Fri Jan 27 17:44:03 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.9` +# Dependencies of `io.spine:spine-test-app:1.9.0-SNAPSHOT.10` ## Runtime 1. **Group:** com.fasterxml.jackson.core **Name:** jackson-annotations **Version:** 2.9.10 @@ -4906,12 +4906,12 @@ This report was generated on **Thu Jan 12 13:43:04 WET 2023** using [Gradle-Lice The dependencies distributed under several licenses, are used according their commercial-use-friendly 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). +This report was generated on **Fri Jan 27 17:44:05 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.9` +# Dependencies of `io.spine:spine-testutil-web:1.9.0-SNAPSHOT.10` ## Runtime 1. **Group:** com.google.android **Name:** annotations **Version:** 4.1.1.4 @@ -5370,12 +5370,12 @@ This report was generated on **Thu Jan 12 13:43:06 WET 2023** using [Gradle-Lice The dependencies distributed under several licenses, are used according their commercial-use-friendly 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). +This report was generated on **Fri Jan 27 17:44:07 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.9` +# Dependencies of `io.spine:spine-web:1.9.0-SNAPSHOT.10` ## Runtime 1. **Group:** com.google.android **Name:** annotations **Version:** 4.1.1.4 @@ -5873,4 +5873,4 @@ This report was generated on **Thu Jan 12 13:43:08 WET 2023** using [Gradle-Lice The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -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 +This report was generated on **Fri Jan 27 17:44:10 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 40a93f8e..52255002 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.9 +1.9.0-SNAPSHOT.10 2015 diff --git a/version.gradle.kts b/version.gradle.kts index b476a09a..211323c5 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.9") +val versionToPublish: String by extra("1.9.0-SNAPSHOT.10") val versionToPublishJs: String by extra(versionToPublish) diff --git a/web/src/main/java/io/spine/web/subscription/BlockingSubscriptionService.java b/web/src/main/java/io/spine/web/subscription/BlockingSubscriptionService.java index 9eb8df91..57da98f6 100644 --- a/web/src/main/java/io/spine/web/subscription/BlockingSubscriptionService.java +++ b/web/src/main/java/io/spine/web/subscription/BlockingSubscriptionService.java @@ -94,8 +94,8 @@ private void checkObserver(MemoizingObserver observer) { throw illegalStateWithCauseOf(error); } else { checkState(observer.isCompleted(), - "Provided SubscriptionService implementation (`%s`) must complete `%s`" + - " procedure at once.", + "Provided `SubscriptionService` implementation (`%s`)" + + " must complete `%s` procedure at once.", subscriptionService, SUBSCRIBE_METHOD_NAME); }