aboutsummaryrefslogtreecommitdiffstats
path: root/app/scripts/lib/observable
diff options
context:
space:
mode:
Diffstat (limited to 'app/scripts/lib/observable')
-rw-r--r--app/scripts/lib/observable/host.js50
-rw-r--r--app/scripts/lib/observable/index.js41
-rw-r--r--app/scripts/lib/observable/local-storage.js37
-rw-r--r--app/scripts/lib/observable/remote.js51
-rw-r--r--app/scripts/lib/observable/util/sync.js24
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