import { MessageDispatcher } from '../message/message.dispatcher';
/**
* Class represents DataObserver
*/
class DataObserver {
/**
* creates new DataObserver instance
*/
constructor() {
/**
* all observables are listed here
* @type {{}}
* @private
*/
this._observables = {};
/**
* all registered DataSignatures without being subscribed
* @type {{}}
* @private
*/
this._signatures = {};
/**
* Map with active subscriptions
* @type {Map}
* @private
*/
this._subscriptions = new Map();
}
/**
* registers a signature
* @param {DataSignature} signature
* @returns {DataObserver}
*/
addSignatures(signature) {
this._signatures[signature.key] = Object.assign({}, signature, { busy: false });
return this;
}
/**
* remove signature
* @param {DataSignature} signature
* @returns {DataObserver}
*/
removeSignature(signature) {
delete this._signatures[signature.key];
return this;
}
/**
* get signature by key
* @param {string} key
* @returns {DataSignature|null}
*/
getSignature(key) {
return (typeof this._signatures[key] === 'object') ? this._signatures[key] : null;
}
/**
* sets busy state to true for a given signature
* while instanciating. Avoids multi instanciating
* same DataPool
* @param {string} key
* @returns {DataObserver}
*/
setSignatureBusy(key) {
this._signatures[key].busy = true;
return this;
}
/**
* checks if signature isBusy
* @param key
* @returns {boolean}
*/
isSignatureBusy(key) {
return this._signatures[key].busy;
}
/**
* adds an observable to internalObservable storage
* @param {string} key
* @param {DataAbstract} observableModule
* @returns {DataObserver}
*/
addObservable(key, observableModule) {
this._observables[key] = {
observable: observableModule.getObservable(),
push: (typeof observableModule.push === 'function') ? observableModule.push.bind(observableModule) : null,
};
return this;
}
/**
* deletes an stored observable by key
* @param {string} key
* @returns {DataObserver}
*/
removeObservable(key) {
delete this._observables[key];
return this;
}
/**
* checks if observable is registered and instanciated by key
* @param {string} key
* @returns {boolean}
* @private
*/
_observableExists(key) {
return (
typeof this._observables[key] === 'object' &&
typeof this._observables[key].observable.subscribe === 'function'
);
}
/**
* checks if signature exists by key
* @param {string} key
* @returns {boolean}
* @private
*/
_signatureExists(key) {
return (typeof this._signatures[key] === 'object');
}
/**
* adds a subscription to the internal
* subscription storage
* @param {ModuleAbstract} origin - unique instance of where this subscription is registered from
* @param {string} key
* @param {Subscription} subscription - RxJs subscription instance
* @returns {DataObserver}
* @private
*/
_addSubscription(origin, key, subscription) {
if(!this._subscriptions.has(origin)) {
this._subscriptions.set(origin, new Set());
}
let currentSubscriptions = this._subscriptions.get(origin);
currentSubscriptions.add({
key,
subscription,
});
this._subscriptions.set(origin, currentSubscriptions);
return this;
}
/**
* get a set of subscriptions by its origin
* @param {ModuleAbstract} origin
* @returns {Set}
*/
getSubscriptions(origin) {
return this._subscriptions.get(origin);
}
/**
* get single subscription by its origin and data key
* @param {ModuleAbstract} origin
* @param {string} key
* @returns {Subscription|null}
*/
getSubscription(origin, key) {
const subscriptions = this.getSubscriptions(origin);
const foundSubscription = subscriptions.filter((subscription) => {
return subscription.key === key;
});
return (foundSubscription.length) ? foundSubscription[0].subscription : null;
}
/**
* checks if a certain subscription exists for its origin
* @param {ModuleAbstract} origin
* @param {string} key
* @returns {boolean}
*/
subscriptionExists(origin, key) {
const subscription = this.getSubscription(origin, key);
return !!subscription;
}
/**
* adds a subscription to a registered Data pool by its key
* @param {ModuleAbstract} origin - unique instance of the subscribers scope
* @param {DataSignature.key} to - DataSignature.key
* @param {function|object} next - callback function on next item or objects with action props
* @param {function} error - callback function on error
* @param {function} complete - callback function on complete queue
* @param {function} filter - filter messages by
*/
subscribe(origin, to, next, error, complete, filter = null) {
if(this._observableExists(to)) {
let nextMethod = (typeof next === 'function')
? next
: (
(typeof next === 'object')
? new MessageDispatcher(next).filter(filter)
: null
);
if(!nextMethod) {
throw new Error('No next method declared calling .subscribe()');
} else if(nextMethod instanceof MessageDispatcher) {
nextMethod = nextMethod.onMessage.bind(nextMethod);
}
const subscription = this._observables[to].observable.subscribe(
nextMethod,
error,
complete
);
this._addSubscription(origin, to, subscription);
} else if(
this._signatureExists(to) &&
!this.isSignatureBusy(to)
) {
this.setSignatureBusy(to);
this.getSignature(to)
.importModule(this)
.then((observableModule) => {
try {
this.addObservable(to, observableModule);
this.removeSignature(to);
if(this._observableExists(to)) {
this.subscribe(origin, to, next, error, complete, filter);
} else {
throw new Error('Observable could not be instanciated. (' + to + ')');
}
} catch (err) {
this.removeSignature(to);
throw new Error(err);
}
});
} else if(
this._signatureExists(to) &&
this.isSignatureBusy(to)
) {
// Retry
window.setTimeout(() => {
this.subscribe(origin, to, next, error, complete, filter);
}, 100);
} else {
throw new Error('Datapool with key: ' + to + ' not exists.');
}
}
/**
* unsubscribe form a certain DataPool by origin and key
* @param {ModuleAbstract} origin
* @param {string} key
* @returns {DataObserver}
*/
unsubscribeFrom(origin, key) {
const subscription = this.getSubscription(origin, key);
if(
subscription &&
typeof subscription.unsubscribe === 'function'
) {
subscription.unsubscribe();
}
return this;
}
/**
* unsubscribe all subscription of a given origin
* @param {ModuleAbstract} origin
* @returns {DataObserver}
*/
unsubscribeAll(origin) {
const subscriptions = this.getSubscriptions(origin);
if(
subscriptions &&
subscriptions instanceof Set &&
subscriptions.size
) {
subscriptions.forEach((subscription) => {
subscription.subscription.unsubscribe();
});
}
return this;
}
/**
* unsubscribe from subscription by origin and optionally key
* @param {ModuleAbstract} origin
* @param {DataSignature.key} from - DataSignature.key
* @returns {DataObserver}
*/
unsubscribe(origin, from = null) {
if(from && this.subscriptionExists(origin, from)) {
this.unsubscribeFrom(origin, from);
} else if(!from) {
this.unsubscribeAll(origin);
}
return this;
}
/**
* Push data to a Data instance
* @param {DataSignature.key} key - DataSignature.key
* @param {Message} message
*/
pushTo(key, message) {
if(this._observableExists(key)) {
if(typeof this._observables[key].push === 'function') {
this._observables[key].push(message);
} else {
throw new Error('Observable (' + key + ') does not provide a .push() method.');
}
}
}
}
export {
DataObserver,
};