statebus 7.0.1 → 7.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/servers.js CHANGED
@@ -37,7 +37,7 @@ function set_options (bus, options) {
37
37
  }
38
38
  }
39
39
 
40
- function import_server (bus, options)
40
+ function import_server (bus, make_statebus, options)
41
41
  { var extra_methods = {
42
42
 
43
43
  serve: function serve (options) {
@@ -76,23 +76,6 @@ function import_server (bus, options)
76
76
  try { bus.http.use(require('compression')())
77
77
  console.log('Enabled http compression!') } catch (e) {}
78
78
 
79
-
80
- // Initialize new clients with an id. We put the client id on
81
- // req.client, and also in a cookie for the browser to see.
82
- if (bus.options.client)
83
- bus.express.use(function (req, res, next) {
84
- req.client = require('cookie').parse(req.headers.cookie || '').client
85
- if (!req.client) {
86
- req.client = (Math.random().toString(36).substring(2)
87
- + Math.random().toString(36).substring(2)
88
- + Math.random().toString(36).substring(2))
89
-
90
- res.setHeader('Set-Cookie', 'client=' + req.client
91
- + '; Expires=21 Oct 2025 00:0:00 GMT;')
92
- }
93
- next()
94
- })
95
-
96
79
  // Initialize file sync
97
80
  ; (bus.options.sync_files || []).forEach( x => {
98
81
  if (require('fs').existsSync(x.fs_path || x.state_path))
@@ -165,27 +148,46 @@ function import_server (bus, options)
165
148
  || req.method === 'OPTIONS')
166
149
  free_the_cors(req, res)
167
150
 
151
+ // Initialize new clients with an id. We put the client id on
152
+ // req.client, and also in a cookie for the browser to see.
153
+ req.peer = //req.headers.peer ||
154
+ require('cookie').parse(req.headers.cookie || '').peer
155
+ if (!req.peer) {
156
+ req.peer = (Math.random().toString(36).substring(2)
157
+ + Math.random().toString(36).substring(2)
158
+ + Math.random().toString(36).substring(2))
159
+
160
+ res.setHeader('Set-Cookie', 'peer=' + req.peer
161
+ + '; Expires=21 Oct 2055 00:0:00 GMT;')
162
+ }
163
+
164
+ // Handle the GET or PUT request!
168
165
  if (req.method === 'GET') {
169
166
 
170
167
  // Make a temporary client bus
171
168
  var cbus = bus.bus_for_http_client(req, res)
172
169
  var cb
173
- function end_it_all () {
170
+ function end_subscription () {
174
171
  cbus.forget(key, cb)
175
- cbus.delete_bus()
176
172
  res.end()
173
+ cbus.http_unsubscribe(req)
177
174
  }
178
175
 
179
176
  braidify(req, res)
180
177
  if (req.subscribe)
181
- res.startSubscription({ onClose: end_it_all })
178
+ res.startSubscription({ onClose: end_subscription })
182
179
  else
183
180
  res.statusCode = 200
184
181
 
182
+ // We only return JSON
183
+ res.setHeader('Content-Type', 'application/json')
184
+
185
185
  // Do the get
186
186
  // cbus.honk = 'statelog'
187
187
  var key = req.path.substr(1)
188
+ // console.log('http_in: doing cbus.get(', key, ')')
188
189
  cbus.get(key, cb = (o) => {
190
+ // console.trace('http_in: Sending update of', key)
189
191
  var body = to_http_body(o)
190
192
 
191
193
  // If we're braiding, send via subscription
@@ -197,14 +199,11 @@ function import_server (bus, options)
197
199
 
198
200
  // And shut down the connection if there's no subscription
199
201
  if (!req.subscribe)
200
- end_it_all()
202
+ end_subscription()
201
203
  })
202
204
  }
203
205
 
204
206
  else if (req.method === 'PUT') {
205
- // Make a temporary client bus
206
- var cbus = bus.bus_for_http_client(req, res)
207
-
208
207
  // Maybe the new state is expressed in patches
209
208
  if (req.headers.patches) {
210
209
  console.error("We don't actually handle PUT patches yet")
@@ -231,9 +230,14 @@ function import_server (bus, options)
231
230
  res.end()
232
231
  return
233
232
  }
234
- cbus.set(obj)
233
+
234
+ // Make a temporary client bus
235
+ var cbus = bus.bus_for_http_client(req, res)
236
+ cbus.set(obj) // And use it
237
+
235
238
  res.statusCode = 200
236
239
  res.end()
240
+ cbus.http_unsubscribe(req)
237
241
  })
238
242
  }
239
243
  }
@@ -243,16 +247,39 @@ function import_server (bus, options)
243
247
 
244
248
  bus_for_http_client: function (req, res) {
245
249
  var bus = this
246
- if (!bus.bus_for_http_client.counter)
250
+ if (!bus.bus_for_http_client.counter) {
247
251
  bus.bus_for_http_client.counter = 0
248
- var cbus = require('./statebus')()
252
+ bus.bus_for_http_client.busses = {}
253
+ }
254
+
255
+ // If this client has a peer ID, and we've got a bus for that peer,
256
+ // then re-use it!
257
+ if (req.peer && bus.bus_for_http_client.busses[req.peer]) {
258
+ var cbus = bus.bus_for_http_client.busses[req.peer]
259
+ cbus.client_ip = req.connection.remoteAddress
260
+ cbus.num_subscriptions++
261
+ return cbus
262
+ }
263
+
264
+ // Otherwise, create a new bus
265
+ var cbus = make_statebus()
249
266
  cbus.label = 'client_http' + bus.bus_for_http_client.counter++
250
267
  cbus.master = bus
268
+ cbus.num_subscriptions = 1
269
+ cbus.http_unsubscribe = (req) => {
270
+ cbus.num_subscriptions--
271
+ if (cbus.num_subscriptions === 0) {
272
+ // log('Last subscription! Killing client bus!')
273
+ cbus.delete_bus()
274
+ delete bus.bus_for_http_client.busses[req.peer]
275
+ }
276
+ }
277
+ bus.bus_for_http_client.busses[req.peer] = cbus
251
278
 
252
- // Log in as the client
279
+ // And log into it as the client
253
280
  cbus.serves_auth({remoteAddress: req.connection.remoteAddress}, bus)
254
281
  bus.options.client(cbus)
255
- cbus.set({key: 'current_user', client: req.client})
282
+ cbus.set({key: 'current_user', val: {client: req.peer}})
256
283
  return cbus
257
284
  },
258
285
 
@@ -355,7 +382,7 @@ function import_server (bus, options)
355
382
  // var client_busses = {} // XXX work in progress
356
383
  var log = master.log
357
384
  if (client_bus_func) {
358
- master.set({key: 'connections'}) // Clean out old sessions
385
+ master.set({key: 'connections', val: {}}) // Clean out old sessions
359
386
  var connections = master.get('connections')
360
387
  }
361
388
  var s = require('sockjs').createServer({
@@ -373,11 +400,11 @@ function import_server (bus, options)
373
400
  // - When disconnecting, decrement the number, and if it gets
374
401
  // to zero, delete the client bus.
375
402
 
376
- connections[conn.id] = {client: conn.id, // client is deprecated
377
- id: conn.id}
403
+ connections.val[conn.id] = {client: conn.id, // client is deprecated
404
+ id: conn.id}
378
405
  master.set(connections)
379
406
 
380
- var client = require('./statebus')()
407
+ var client = make_statebus()
381
408
  client.label = 'client' + client_num++
382
409
  master.label = master.label || 'master'
383
410
  client.master = master
@@ -486,7 +513,7 @@ function import_server (bus, options)
486
513
  for (var key in our_gets_in)
487
514
  client.forget(key, sockjs_pubber)
488
515
  if (client_bus_func) {
489
- delete connections[conn.id]; master.set(connections)
516
+ delete connections.val[conn.id]; master.set(connections)
490
517
  master.delete('connection/' + conn.id)
491
518
  client.delete_bus()
492
519
  }
@@ -534,12 +561,12 @@ function import_server (bus, options)
534
561
  client('connections').to_set = function noop (t) {t.abort()}
535
562
  client('connections').to_get = function () {
536
563
  var result = []
537
- var conns = master.get('connections')
564
+ var conns = master.get('connections').val
538
565
  for (var connid in conns)
539
566
  if (connid !== 'key')
540
567
  result.push(client.get('connection/' + connid))
541
568
 
542
- return {all: result}
569
+ return {val: result}
543
570
  }
544
571
  }
545
572
  })
@@ -555,13 +582,6 @@ function import_server (bus, options)
555
582
  WebSocket = require('websocket').w3cwebsocket
556
583
  return new WebSocket(url + '/' + bus.options.websocket_path + '/websocket')
557
584
  },
558
- client_creds: function client_creds (server_url) {
559
- // Right now the server just creates a different random id each time
560
- // it connects.
561
- return {clientid: (Math.random().toString(36).substring(2)
562
- + Math.random().toString(36).substring(2)
563
- + Math.random().toString(36).substring(2))}
564
- },
565
585
 
566
586
  file_store: (function () {
567
587
  // Make a database
@@ -1307,23 +1327,24 @@ function import_server (bus, options)
1307
1327
  }
1308
1328
 
1309
1329
  function user_addy (u) {
1330
+ console.error('fix this! not ported to v7 links')
1310
1331
  return u.name + '@' + opts.domain
1311
1332
  }
1312
1333
  function current_addy () {
1313
1334
  var c = client.get('current_user')
1314
- return c.logged_in ? user_addy(c.user) : 'public'
1335
+ console.error('fix this! not ported to v7 links')
1336
+ return c.val.logged_in ? user_addy(c.val.user) : 'public'
1315
1337
  }
1316
1338
  function is_author (post) {
1317
1339
  var from = post._.from
1318
- var c = client.get('current_user')
1319
1340
  return from.includes(current_addy())
1320
1341
  }
1321
1342
  function can_see (post) {
1322
- var c = client.get('current_user')
1343
+ console.error('fix this! not ported to v7')
1323
1344
  var allowed = post._.to.concat(post._.cc).concat(post._.from)
1324
1345
  return allowed.includes(current_addy()) || allowed.includes('public')
1325
1346
  }
1326
- var drtbus = master//require('./statebus')()
1347
+ var drtbus = master//make_statebus()
1327
1348
  function dirty (key) { drtbus.set({key: 'dirty-'+key, n: Math.random()}) }
1328
1349
  function watch_for_dirt (key) { drtbus.get('dirty-'+key) }
1329
1350
 
@@ -1334,7 +1355,6 @@ function import_server (bus, options)
1334
1355
  // Define state on client
1335
1356
  client('posts*').to_get = (k, rest) => {
1336
1357
  var args = bus.parse(rest.substr(1))
1337
- var c = client.get('current_user')
1338
1358
  args.for = current_addy()
1339
1359
  var e = master.get('posts_for/' + JSON.stringify(args))
1340
1360
  return {_: master.clone(e._).map(x=>client.get(x.key))}
@@ -1412,7 +1432,7 @@ function import_server (bus, options)
1412
1432
  }
1413
1433
 
1414
1434
  client('friends').to_get = t => {
1415
- return {_: (master.get('users').all||[])
1435
+ return {_: (master.get('users').val||[])
1416
1436
  .map(u=>client.get('email/' + user_addy(u)))
1417
1437
  .concat([client.get('email/public')])
1418
1438
  }
@@ -1640,42 +1660,55 @@ function import_server (bus, options)
1640
1660
 
1641
1661
  serves_auth: function serves_auth (conn, master) {
1642
1662
  var client = this // to keep me straight while programming
1643
-
1644
- function logout (key) {
1663
+ function logout (user_key) {
1645
1664
  var clients = master.get('logged_in_clients')
1646
- for (var k in clients)
1647
- if (k !== 'key')
1648
- if (Object.keys(clients[k]).length === 0) {
1649
- client.log('Found a deleted user. Removing.', k, clients[k])
1650
- delete clients[k]
1651
- master.set(clients)
1652
- } else if (clients[k].key === key) {
1653
- client.log('Logging out!', k, clients[k])
1654
- delete clients[k]
1655
- master.set(clients)
1656
- }
1665
+ clients.val = clients.val || {}
1666
+
1667
+ for (var k in clients.val) {
1668
+ // We used to encounter stray deleted accounts that hadn't
1669
+ // gotten properly garbage collected, and I added some code
1670
+ // here to remove them.
1671
+ //
1672
+ // However, it was a bit kludgy, and I'm rewriting the code
1673
+ // now, and not sure if this problem still occurs. Will watch
1674
+ // and see. Meanwhile, I'm commenting the code out.
1675
+ //
1676
+ // if (Object.keys(clients.val[k]).length === 0) {
1677
+ // client.log('Found a deleted user. Removing.', k, clients.val[k])
1678
+ // delete clients.val[k]
1679
+ // master.set(clients)
1680
+ // }
1681
+ if (clients.val[k].link === user_key) {
1682
+ client.log('Logging out!', k, clients.val[k])
1683
+ delete clients.val[k]
1684
+ master.set(clients)
1685
+ }
1686
+ }
1657
1687
  }
1658
1688
 
1659
1689
  // Initialize master
1660
1690
  if (!master.auth_initialized) {
1691
+
1692
+ // A hash for fast lookup of a user's password
1661
1693
  master('users/passwords').to_get = function (k) {
1694
+ // We compute it from the 'users' state
1662
1695
  master.log('users/passwords.to_get: Computing!')
1663
- var result = {key: 'users/passwords'}
1696
+ var result = {key: 'users/passwords', val: {}}
1664
1697
  var users = master.get('users')
1665
- users.all = users.all || []
1666
- for (var i=0; i<users.all.length; i++) {
1667
- var u = master.get(users.all[i])
1668
- if (!(u.login || u.name)) {
1669
- console.error('upass: this user has bogus name/login', u.key, u.name, u.login)
1698
+ users.val = users.val || []
1699
+ for (var i=0; i<users.val.length; i++) {
1700
+ var u = master.get(users.val[i].link)
1701
+ if (!(u.val.login || u.val.name)) {
1702
+ console.error('upass: this user has bogus name/login', u.key, u.val.name, u.val.login)
1670
1703
  continue
1671
1704
  }
1672
- var login = (u.login || u.name).toLowerCase()
1705
+ var login = (u.val.login || u.val.name).toLowerCase()
1673
1706
  console.assert(login, 'Missing login for user', u)
1674
- if (result.hasOwnProperty(login)) {
1707
+ if (result.val.hasOwnProperty(login)) {
1675
1708
  console.error("upass: this user's name is bogus, dude.", u.key)
1676
1709
  continue
1677
1710
  }
1678
- result[login] = {user: u.key, pass: u.pass}
1711
+ result.val[login] = {user: u.key, pass: u.val.pass}
1679
1712
  }
1680
1713
  return result
1681
1714
  }
@@ -1686,26 +1719,25 @@ function import_server (bus, options)
1686
1719
  master.log('Deleteinggg!!!', key)
1687
1720
  // Remove from users.all
1688
1721
  var users = master.get('users')
1689
- users.all = users.all.filter(u => u.key && u.key !== key)
1722
+ users.val = users.val.filter(u => u.link && u.link !== key)
1690
1723
  master.set(users)
1691
1724
 
1692
1725
  // Log out
1693
1726
  master.log('Logging out', key)
1694
1727
  logout(key)
1695
1728
 
1696
- // Remove connection
1697
- master.log('Removing connections for', key)
1698
- var conns = master.get('connections')
1699
- for (var k in conns) {
1700
- console.log('Trying key', k)
1701
- if (k !== 'key')
1702
- if (conns[k].user && !conns[k].user.key) {
1703
- console.log('Cleaning keyless user', conss[k].user)
1704
- delete conns[k].user
1705
- master.set(conns)
1706
- continue
1707
- }
1708
- }
1729
+ // // Remove connection
1730
+ // master.log('Removing connections for', key)
1731
+ // var conns = master.get('connections')
1732
+ // for (var k in conns.val) {
1733
+ // console.log('Trying key', k)
1734
+ // if (conns.val[k].user && !conns.val[k].user.key) {
1735
+ // console.log('Cleaning keyless user', conss.val[k].user)
1736
+ // delete conns.val[k].user
1737
+ // master.set(conns)
1738
+ // continue
1739
+ // }
1740
+ // }
1709
1741
 
1710
1742
  master.log('Dirtying users/passwords for', key)
1711
1743
  master.dirty('users/passwords')
@@ -1717,9 +1749,9 @@ function import_server (bus, options)
1717
1749
 
1718
1750
  // Authentication functions
1719
1751
  function authenticate (login, pass) {
1720
- var userpass = master.get('users/passwords')[login.toLowerCase()]
1752
+ var userpass = master.get('users/passwords').val[login.toLowerCase()]
1721
1753
  master.log('authenticate: we see', {
1722
- passwords: master.get('users/passwords'),
1754
+ passwords: master.get('users/passwords').val,
1723
1755
  hash_to_match: userpass && userpass.pass,
1724
1756
  password_guess: pass
1725
1757
  })
@@ -1730,7 +1762,7 @@ function import_server (bus, options)
1730
1762
 
1731
1763
  // console.log('comparing passwords', pass, userpass.pass)
1732
1764
  if (require('bcrypt-nodejs').compareSync(pass, userpass.pass))
1733
- return master.get(userpass.user)
1765
+ return userpass.user
1734
1766
  }
1735
1767
  function create_account (params) {
1736
1768
  if (typeof (params.login || params.name) !== 'string')
@@ -1743,7 +1775,7 @@ function import_server (bus, options)
1743
1775
  throw 'invalid name, login, pass, or email'
1744
1776
 
1745
1777
  var passes = master.get('users/passwords')
1746
- if (passes.hasOwnProperty(login))
1778
+ if (passes.val.hasOwnProperty(login))
1747
1779
  throw 'there is already a user with that login or name'
1748
1780
 
1749
1781
  // Hash password
@@ -1763,60 +1795,81 @@ function import_server (bus, options)
1763
1795
  key = 'user/' + Math.random().toString(36).substring(7,13)
1764
1796
 
1765
1797
  // Make account object
1766
- var new_account = {key: key,
1767
- name: params.name,
1768
- login: params.login,
1769
- pass: params.pass,
1770
- email: params.email }
1771
- for (var k in new_account) if (!new_account[k]) delete new_account[k]
1798
+ var new_account = {
1799
+ key: key, val: {
1800
+ name: params.name,
1801
+ login: params.login,
1802
+ pass: params.pass,
1803
+ email: params.email
1804
+ }
1805
+ }
1806
+ for (var k in new_account.val) // Clean out falsy fields
1807
+ if (!new_account.val[k])
1808
+ delete new_account.val[k]
1772
1809
  master.set(new_account)
1773
1810
 
1774
1811
  var users = master.get('users')
1775
- users.all = users.all || []
1776
- users.all.push(new_account)
1777
- passes[login] = {user: new_account.key, pass: new_account.pass}
1812
+ users.val = users.val || []
1813
+ users.val.push({link: new_account.key})
1814
+ passes.val[login] = {user: new_account.key, pass: new_account.val.pass}
1778
1815
  master.set(users)
1779
1816
  master.set(passes)
1780
1817
  }
1781
1818
 
1782
1819
  // Current User
1783
1820
  client('current_user').to_get = function (k) {
1784
- client.log('* getting: current_user')
1785
- if (!conn.client) return
1786
- var u = master.get('logged_in_clients')[conn.client]
1787
- u = u && user_obj(u.key, true)
1788
- return {user: u || null, logged_in: !!u}
1821
+ if (!conn.client)
1822
+ return {val: {error: 'no client'}}
1823
+
1824
+ var u = (master.get('logged_in_clients').val || {})[conn.client]
1825
+ var result = {val: {user: u || null, logged_in: !!u}}
1826
+ return result
1789
1827
  }
1790
1828
 
1791
1829
  client('current_user').to_set = function (o, t) {
1830
+ var val = o.val
1831
+ if (typeof val !== 'object') {
1832
+ console.error('current_user: set to something not an object:', val)
1833
+ t.abort()
1834
+ return
1835
+ }
1792
1836
  function error (msg) {
1837
+ console.error(msg)
1793
1838
  client.set.abort(o)
1794
1839
  var c = client.get('current_user')
1795
- c.error = msg
1840
+ c.val.error = msg
1796
1841
  client.set.fire(c)
1797
1842
  }
1798
1843
 
1799
1844
  client.log('* saving: current_user!')
1800
- if (o.client && !conn.client) {
1845
+ if (val.client && !conn.client) {
1801
1846
  // Set the client
1802
- conn.client = o.client
1803
- client.client_id = o.client
1847
+ //
1848
+ // Note: Should this code be moved upstream, when creating a
1849
+ // client bus?
1850
+ conn.client = val.client
1851
+ client.client_id = val.client
1804
1852
  client.client_ip = conn.remoteAddress
1805
1853
 
1806
- if (conn.id) {
1807
- var connections = master.get('connections')
1808
- connections[conn.id].user = master.get('logged_in_clients')[conn.client]
1809
- master.set(connections)
1810
- }
1854
+ // if (conn.id) {
1855
+ // var connections = master.get('connections')
1856
+ // var logged_in_user =
1857
+ // (master.get('logged_in_clients').val || {})[conn.client]
1858
+
1859
+ // if (logged_in_user) {
1860
+ // connections.val[conn.id].user = {link: user.link}
1861
+ // master.set(connections)
1862
+ // }
1863
+ // }
1811
1864
  }
1812
1865
  else {
1813
- if (o.create_account) {
1866
+ if (val.create_account) {
1814
1867
  client.log('current_user: creating account')
1815
1868
  try {
1816
- create_account(o.create_account)
1869
+ create_account(val.create_account)
1817
1870
  client.log('Success creating account!')
1818
1871
  var cu = client.get('current_user')
1819
- cu.create_account = null
1872
+ cu.val.create_account = null
1820
1873
  client.set.fire(cu)
1821
1874
  } catch (e) {
1822
1875
  error('Cannot create that account because ' + e)
@@ -1824,29 +1877,29 @@ function import_server (bus, options)
1824
1877
  }
1825
1878
  }
1826
1879
 
1827
- if (o.login_as && conn.id) {
1880
+ if (val.login_as) {
1828
1881
  // Then client is trying to log in
1829
- client.log('current_user: trying to log in')
1830
- var creds = o.login_as
1882
+ var creds = val.login_as
1831
1883
  var login = creds.login || creds.name
1832
1884
  if (login && creds.pass) {
1833
1885
  // With a username and password
1834
- var u = authenticate(login, creds.pass)
1886
+ var user_key = authenticate(login, creds.pass)
1835
1887
 
1836
- client.log('auth said:', u)
1837
- if (u) {
1888
+ client.log('auth said:', user_key)
1889
+ if (user_key) {
1838
1890
  // Success!
1839
1891
  // Associate this user with this session
1840
1892
  // user.log('Logging the user in!', u)
1841
1893
 
1842
1894
  var clients = master.get('logged_in_clients')
1843
- var connections = master.get('connections')
1895
+ // var connections = master.get('connections')
1844
1896
 
1845
- clients[conn.client] = u
1846
- connections[conn.id].user = u
1897
+ clients.val = clients.val || {}
1898
+ clients.val[conn.client] = {link: user_key}
1899
+ // connections.val[conn.id].user = {link: user_key}
1847
1900
 
1848
1901
  master.set(clients)
1849
- master.set(connections)
1902
+ // master.set(connections)
1850
1903
 
1851
1904
  client.log('current_user: success logging in!')
1852
1905
  }
@@ -1861,16 +1914,17 @@ function import_server (bus, options)
1861
1914
  }
1862
1915
  }
1863
1916
 
1864
- else if (o.logout && conn.id) {
1917
+ else if (val.logout) {
1865
1918
  client.log('current_user: logging out')
1866
1919
  var clients = master.get('logged_in_clients')
1867
- var connections = master.get('connections')
1920
+ // var connections = master.get('connections')
1868
1921
 
1869
- delete clients[conn.client]
1870
- connections[conn.id].user = null
1922
+ clients.val = clients.val || {}
1923
+ delete clients.val[conn.client]
1924
+ // connections.val[conn.id].user = null
1871
1925
 
1872
1926
  master.set(clients)
1873
- master.set(connections)
1927
+ // master.set(connections)
1874
1928
  }
1875
1929
  }
1876
1930
 
@@ -1888,11 +1942,13 @@ function import_server (bus, options)
1888
1942
  var user_key = o.key.match(/^user\/([^\/]+)/)
1889
1943
  user_key = user_key && ('user/' + user_key[1])
1890
1944
 
1891
- // Only the current user can touch themself.
1892
- if (!c.logged_in || c.user.key !== user_key) {
1893
- client.log('Only the current user can touch themself.',
1894
- {logged_in: c.logged_in, as: c.user && c.user.key,
1895
- touching: user_key})
1945
+ // Only the current user can touch himself.
1946
+ if (!c.val.logged_in || c.val.user.link !== user_key) {
1947
+ client.log('Only the current user can touch himself.', {
1948
+ logged_in: c.val.logged_in,
1949
+ as: c.val.user && c.val.user.link,
1950
+ touching: user_key
1951
+ })
1896
1952
  client.set.abort(o)
1897
1953
  return
1898
1954
  }
@@ -1908,9 +1964,14 @@ function import_server (bus, options)
1908
1964
  console.assert(o.key.match(/^user\/[^\/]+$/))
1909
1965
 
1910
1966
  // Validate types
1911
- if (!client.validate(o, {key: 'string', '?login': 'string', '?name': 'string',
1912
- '?pass': 'string', '?email': 'string', /*'?pic': 'string',*/
1913
- '*':'*'})) {
1967
+ if (!client.validate(o, {key: 'string',
1968
+ val: {
1969
+ '?login': 'string',
1970
+ '?name': 'string',
1971
+ '?pass': 'string',
1972
+ '?email': 'string',
1973
+ /*'?pic': 'string',*/
1974
+ '*':'*'}})) {
1914
1975
  client.log('This user change fails validation.')
1915
1976
  client.set.abort(o)
1916
1977
  return
@@ -1921,7 +1982,7 @@ function import_server (bus, options)
1921
1982
  // • That resulting login must be unique across all users
1922
1983
 
1923
1984
  // There must be at least a login or a name
1924
- var login = o.login || o.name
1985
+ var login = o.val.login || o.val.name
1925
1986
  if (!login) {
1926
1987
  client.log('User must have a login or a name')
1927
1988
  client.set.abort(o)
@@ -1932,26 +1993,26 @@ function import_server (bus, options)
1932
1993
  var userpass = master.get('users/passwords')
1933
1994
 
1934
1995
  // Validate that the login/name is not changed to something clobberish
1935
- var old_login = u.login || u.name
1996
+ var old_login = u.val.login || u.val.name
1936
1997
  if (old_login.toLowerCase() !== login.toLowerCase()
1937
1998
  && userpass.hasOwnProperty(login)) {
1938
1999
  client.log('The login', login, 'is already taken. Aborting.')
1939
2000
  client.set.abort(o) // Abort
1940
2001
 
1941
2002
  o = client.get(o.key) // Add error message
1942
- o.error = 'The login "' + login + '" is already taken'
2003
+ o.val.error = 'The login "' + login + '" is already taken'
1943
2004
  client.set.fire(o)
1944
2005
 
1945
2006
  return // And exit
1946
2007
  }
1947
2008
 
1948
2009
  // Now we can update login and name
1949
- u.login = o.login
1950
- u.name = o.name
2010
+ u.val.login = o.val.login
2011
+ u.val.name = o.val.name
1951
2012
 
1952
2013
  // Hash password
1953
- o.pass = o.pass && require('bcrypt-nodejs').hashSync(o.pass)
1954
- u.pass = o.pass || u.pass
2014
+ o.val.pass = o.val.pass && require('bcrypt-nodejs').hashSync(o.val.pass)
2015
+ u.val.pass = o.val.pass || u.val.pass
1955
2016
 
1956
2017
  // // Users can have pictures (remove this soon)
1957
2018
  // // Bug: if user changes name, this picture's url doesn't change.
@@ -1970,24 +2031,24 @@ function import_server (bus, options)
1970
2031
 
1971
2032
  // For anything else, go ahead and add it to the user object
1972
2033
  var protected = {key:1, name:1, /*pic:1,*/ pass:1}
1973
- for (var k in o)
2034
+ for (var k in o.val)
1974
2035
  if (!protected.hasOwnProperty(k))
1975
- u[k] = o[k]
1976
- for (var k in u)
1977
- if (!protected.hasOwnProperty(k) && !o.hasOwnProperty(k))
1978
- delete u[k]
2036
+ u.val[k] = o.val[k]
2037
+ for (var k in u.val)
2038
+ if (!protected.hasOwnProperty(k) && !o.val.hasOwnProperty(k))
2039
+ delete u.val[k]
1979
2040
 
1980
2041
  master.set(u)
1981
2042
  }
1982
2043
  client('user/*').to_get = function user_getter (k) {
1983
2044
  var c = client.get('current_user')
1984
- client.log('* getting:', k, 'as', c.user)
2045
+ client.log('* getting:', k, 'as', c.val.user)
1985
2046
 
1986
2047
  // Users have closet space at /user/<name>/*
1987
2048
  if (k.match(closet_space_key)) {
1988
2049
  var obj_user = k.match(closet_space_key)[1]
1989
2050
  if (k.match(private_closet_space_key)
1990
- && (!c.user || obj_user !== c.user.key)) {
2051
+ && (!c.val.user || obj_user !== c.val.user.link)) {
1991
2052
  client.log('hiding private closet data')
1992
2053
  return {}
1993
2054
  }
@@ -1996,16 +2057,17 @@ function import_server (bus, options)
1996
2057
  }
1997
2058
 
1998
2059
  // Otherwise return the actual user
1999
- return user_obj(k, c.logged_in && c.user.key === k)
2060
+ return user_obj(k, c.val.logged_in && c.val.user.link === k)
2000
2061
  }
2001
2062
  client('user/*').to_delete = function () {}
2002
2063
  function user_obj (k, logged_in) {
2003
2064
  var o = master.clone(master.get(k))
2004
2065
  if (k.match(/^user\/([^\/]+)\/private\/(.*)$/))
2005
2066
  return logged_in ? o : {key: k}
2006
-
2007
- delete o.pass
2008
- if (!logged_in) {delete o.email; delete o.login}
2067
+
2068
+ o.val = o.val || {}
2069
+ delete o.val.pass
2070
+ if (!logged_in) {delete o.val.email; delete o.val.login}
2009
2071
  return o
2010
2072
  }
2011
2073
 
@@ -2024,14 +2086,14 @@ function import_server (bus, options)
2024
2086
  var was_logged_in = undefined
2025
2087
 
2026
2088
  function client_prefix (current_user) {
2027
- return 'client/' + (current_user.logged_in
2028
- ? current_user.user.key.substr('user/'.length)
2089
+ return 'client/' + (current_user.val.logged_in
2090
+ ? current_user.val.user.link.substr('user/'.length)
2029
2091
  : client.client_id) + '/'
2030
2092
  }
2031
2093
 
2032
- function copy_client_to_user(client, user) {
2094
+ function copy_client_to_user(client, user_key) {
2033
2095
  var old_prefix = 'client/' + client.client_id
2034
- var new_prefix = 'client/' + user.key.substr('user/'.length)
2096
+ var new_prefix = 'client/' + user_key.substr('user/'.length)
2035
2097
 
2036
2098
  var keys = client.master.get('persisted_keys/' + client.client_id)
2037
2099
  if (!keys.val) return
@@ -2077,10 +2139,10 @@ function import_server (bus, options)
2077
2139
  client(_=>{
2078
2140
  var c = client.get('current_user')
2079
2141
  if (client.loading()) return
2080
- if (was_logged_in == false && c.logged_in)
2142
+ if (was_logged_in == false && c.val.logged_in)
2081
2143
  // User just logged in! Let's copy his stuff over
2082
- copy_client_to_user(client, c.user)
2083
- was_logged_in = c.logged_in
2144
+ copy_client_to_user(client, c.val.user.link)
2145
+ was_logged_in = c.val.logged_in
2084
2146
  })
2085
2147
 
2086
2148
  client(prefix_to_sync).to_get = function (key) {
@@ -2138,7 +2200,7 @@ function import_server (bus, options)
2138
2200
  }
2139
2201
 
2140
2202
  function client_persisted_keys () {
2141
- if (client.get('current_user').logged_in) return
2203
+ if (client.get('current_user').val.logged_in) return
2142
2204
  var result = client.master.get('persisted_keys/' + client.client_id)
2143
2205
  if (result && !result.val) result.val = {}
2144
2206
  return result
@@ -2271,7 +2333,7 @@ function import_server (bus, options)
2271
2333
  // Installs a GET handler at route that gets state from a getter function
2272
2334
  // Note: Makes too many textbusses. Should re-use one.
2273
2335
  http_serve: function http_serve (route, getter) {
2274
- var textbus = require('./statebus')()
2336
+ var textbus = make_statebus()
2275
2337
  textbus.label = 'textbus'
2276
2338
  var watched = new Set()
2277
2339
  textbus('*').to_get = (filename, old) => {