diff options
Diffstat (limited to 'app/scripts/lib/observable')
-rw-r--r-- | app/scripts/lib/observable/host.js | 50 | ||||
-rw-r--r-- | app/scripts/lib/observable/index.js | 41 | ||||
-rw-r--r-- | app/scripts/lib/observable/local-storage.js | 37 | ||||
-rw-r--r-- | app/scripts/lib/observable/remote.js | 51 | ||||
-rw-r--r-- | app/scripts/lib/observable/util/sync.js | 24 |
5 files changed, 203 insertions, 0 deletions
diff --git a/app/scripts/lib/observable/host.js b/app/scripts/lib/observable/host.js new file mode 100644 index 000000000..d1b110503 --- /dev/null +++ b/app/scripts/lib/observable/host.js @@ -0,0 +1,50 @@ +const Dnode = require('dnode') +const ObservableStore = require('./index') +const endOfStream = require('end-of-stream') + +// +// HostStore +// +// plays host to many RemoteStores and sends its state over a stream +// + +class HostStore extends ObservableStore { + + constructor (initState, opts) { + super(initState) + this._opts = opts || {} + } + + createStream () { + const self = this + // setup remotely exposed api + let remoteApi = {} + if (!self._opts.readOnly) { + remoteApi.put = (newState) => self.put(newState) + } + // listen for connection to remote + const dnode = Dnode(remoteApi) + dnode.on('remote', (remote) => { + // setup update subscription lifecycle + const updateHandler = (state) => remote.put(state) + self._onConnect(updateHandler) + endOfStream(dnode, () => self._onDisconnect(updateHandler)) + }) + return dnode + } + + _onConnect (updateHandler) { + // subscribe to updates + this.subscribe(updateHandler) + // send state immediately + updateHandler(this.get()) + } + + _onDisconnect (updateHandler) { + // unsubscribe to updates + this.unsubscribe(updateHandler) + } + +} + +module.exports = HostStore diff --git a/app/scripts/lib/observable/index.js b/app/scripts/lib/observable/index.js new file mode 100644 index 000000000..1ff112e95 --- /dev/null +++ b/app/scripts/lib/observable/index.js @@ -0,0 +1,41 @@ +const EventEmitter = require('events').EventEmitter + +class ObservableStore extends EventEmitter { + + constructor (initialState) { + super() + this._state = initialState + } + + // wrapper around internal get + get () { + return this._state + } + + // wrapper around internal put + put (newState) { + this._put(newState) + } + + // subscribe to changes + subscribe (handler) { + this.on('update', handler) + } + + // unsubscribe to changes + unsubscribe (handler) { + this.removeListener('update', handler) + } + + // + // private + // + + _put (newState) { + this._state = newState + this.emit('update', newState) + } + +} + +module.exports = ObservableStore diff --git a/app/scripts/lib/observable/local-storage.js b/app/scripts/lib/observable/local-storage.js new file mode 100644 index 000000000..6ed3860f6 --- /dev/null +++ b/app/scripts/lib/observable/local-storage.js @@ -0,0 +1,37 @@ +const ObservableStore = require('./index') + +// +// LocalStorageStore +// +// uses localStorage instead of a cache +// + +class LocalStorageStore extends ObservableStore { + + constructor (opts) { + super() + delete this._state + + this._opts = opts || {} + if (!this._opts.storageKey) { + throw new Error('LocalStorageStore - no "storageKey" specified') + } + this._storageKey = this._opts.storageKey + } + + get() { + try { + return JSON.parse(global.localStorage[this._storageKey]) + } catch (err) { + return undefined + } + } + + _put(newState) { + global.localStorage[this._storageKey] = JSON.stringify(newState) + this.emit('update', newState) + } + +} + +module.exports = LocalStorageStore diff --git a/app/scripts/lib/observable/remote.js b/app/scripts/lib/observable/remote.js new file mode 100644 index 000000000..603f6f0b8 --- /dev/null +++ b/app/scripts/lib/observable/remote.js @@ -0,0 +1,51 @@ +const Dnode = require('dnode') +const ObservableStore = require('./index') +const endOfStream = require('end-of-stream') + +// +// RemoteStore +// +// connects to a HostStore and receives its latest state +// + +class RemoteStore extends ObservableStore { + + constructor (initState, opts) { + super(initState) + this._opts = opts || {} + this._remote = null + } + + put (newState) { + if (!this._remote) throw new Error('RemoteStore - "put" called before connection to HostStore') + this._put(newState) + this._remote.put(newState) + } + + createStream () { + const self = this + const dnode = Dnode({ + put: (newState) => self._put(newState), + }) + // listen for connection to remote + dnode.once('remote', (remote) => { + // setup connection lifecycle + self._onConnect(remote) + endOfStream(dnode, () => self._onDisconnect()) + }) + return dnode + } + + _onConnect (remote) { + this._remote = remote + this.emit('connected') + } + + _onDisconnect () { + this._remote = null + this.emit('disconnected') + } + +} + +module.exports = RemoteStore
\ No newline at end of file diff --git a/app/scripts/lib/observable/util/sync.js b/app/scripts/lib/observable/util/sync.js new file mode 100644 index 000000000..c61feb02e --- /dev/null +++ b/app/scripts/lib/observable/util/sync.js @@ -0,0 +1,24 @@ + +// +// synchronizeStore(inStore, outStore, stateTransform) +// +// keeps outStore synchronized with inStore, via an optional stateTransform +// + +module.exports = synchronizeStore + + +function synchronizeStore(inStore, outStore, stateTransform) { + stateTransform = stateTransform || transformNoop + const initState = stateTransform(inStore.get()) + outStore.put(initState) + inStore.subscribe((inState) => { + const outState = stateTransform(inState) + outStore.put(outState) + }) + return outStore +} + +function transformNoop(state) { + return state +}
\ No newline at end of file |