/*
* 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 {ActorRequestFactory} from "./actor-request-factory";
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
*
* @property {!Array<Object>} protoIndexFiles
* 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
* the provider of the user interacting with Spine
* @property {?Client} implementation
* the custom implementation of `Client`
* @property {?Routing} routing
* custom configuration of HTTP endpoints
* @property {?TenantProvider} tenantProvider
* the provider of an active tenant ID, if not specified, the application is considered
* single-tenant
* @property {?Duration} subscriptionKeepUpInterval
* the custom interval for sending requests to keep up subscriptions
*/
/**
* An abstract factory for creation of `Client` instances.
*
* Ensures that the `ClientOptions` contain 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}
* and performs registration of types and parsers containing in these files.
*
* Creation of the concrete implementation of `Client` instances is delegated to inheritors.
*/
export class AbstractClientFactory {
/**
* Creates a new instance of `Client` implementation in accordance with given options.
*
* @param {!ClientOptions} options client initialization options
* @return {Client} a `Client` instance
*/
static createClient(options) {
this._ensureOptionsSufficient(options);
return this._clientFor(options);
}
/**
* Creates a new instance of `Client` implementation in accordance with given options.
*
* @param {!ClientOptions} options
* @return {Client}
* @protected
*/
static _clientFor(options) {
throw new Error('Not implemented in abstract base')
}
/**
* Creates a new instance of `QueryingClient` implementation in accordance with given options.
*
* @param {!ClientOptions} options client initialization options
* @return {QueryingClient} a `QueryingClient` instance
*/
static createQuerying(options) {
throw new Error('Not implemented in abstract base')
}
/**
* Creates a new instance of `SubscribingClient` implementation in accordance with given options.
*
* @param {!ClientOptions} options client initialization options
* @return {SubscribingClient} a `SubscribingClient` instance
*/
static createSubscribing(options) {
throw new Error('Not implemented in abstract base')
}
/**
* Creates a new instance of `CommandingClient` implementation in accordance with given options.
*
* @param {!ClientOptions} options client initialization options
* @return {CommandingClient} a `CommandingClient` instance
*/
static createCommanding(options) {
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.
*
* @param {!ClientOptions} options
* @protected
*/
static _ensureOptionsSufficient(options) {
if (!options) {
throw new Error('Unable to initialize client. The `ClientOptions` is undefined.');
}
const indexFiles = options.protoIndexFiles;
if (!Array.isArray(indexFiles) || indexFiles.length === 0) {
throw new Error('Only a non-empty array is allowed as ClientOptions.protoIndexFiles parameter.');
}
indexFiles.forEach(indexFile => {
if (typeof indexFile !== 'object'
|| !(indexFile.types instanceof Map)
|| !(indexFile.parsers instanceof Map) ) {
throw new Error('Unable to register Protobuf index files.' +
' Check the `ClientOptions.protoIndexFiles` contains files' +
' generated with "io.spine.tools:spine-proto-js-plugin".');
}
});
}
}
/**
* An implementation of the `AbstractClientFactory` that returns a client instance
* provided in `ClientOptions` parameter.
*/
export class CustomClientFactory extends AbstractClientFactory {
/**
* Returns a custom `Client` implementation provided in options. Expects that the given options
* contain an implementation which extends `Client`.
*
* Can be used to provide mock implementations of `Client`.
*
* @param {ClientOptions} options
* @return {Client} a custom `Client` implementation provided in options
* @override
*/
static _clientFor(options) {
return options.implementation;
}
/**
* Returns a custom `QueryingClient` implementation provided in options. Expects that the given
* options contain an implementation which extends `QueryingClient`.
*
* Can be used to provide mock implementations of `QueryingClient`.
*
* @param {ClientOptions} options
* @return {QueryingClient} a custom `QueryingClient` implementation provided in options
* @override
*/
static createQuerying(options) {
return options.implementation;
}
/**
* Returns a custom `SubscribingClient` implementation provided in options. Expects that the given
* options contain an implementation which extends `SubscribingClient`.
*
* Can be used to provide mock implementations of `SubscribingClient`.
*
* @param {ClientOptions} options
* @return {SubscribingClient} a custom `SubscribingClient` implementation provided in options
* @override
*/
static createSubscribing(options) {
return options.implementation;
}
/**
* Returns a custom `CommandingClient` implementation provided in options. Expects that the given
* options contain an implementation which extends `CommandingClient`.
*
* Can be used to provide mock implementations of `CommandingClient`.
*
* @param {ClientOptions} options
* @return {CommandingClient} a custom `CommandingClient` implementation provided in options
* @override
*/
static createCommanding(options) {
return options.implementation;
}
/**
* @override
*/
static _ensureOptionsSufficient(options) {
super._ensureOptionsSufficient(options);
const customClient = options.implementation;
if (!customClient || !(customClient instanceof Client)) {
throw new Error('Unable to initialize custom client implementation.' +
' The `ClientOptions.implementation` must extend `Client`.');
}
}
}