statebus 6.1.21 → 7.0.1

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.
@@ -2,6 +2,7 @@
2
2
  var websocket_prefix = (clientjs_option('websocket_path')
3
3
  || '_connect_to_statebus_')
4
4
 
5
+ // Todo: remove this global
5
6
  window.dom = window.dom || new Proxy({}, {
6
7
  get: function (o, k) { return o[k] },
7
8
  set: function (o, k, v) {
@@ -42,7 +43,7 @@
42
43
  // return new SockJS(url + '/' + websocket_prefix)
43
44
  }
44
45
  function client_creds (server_url) {
45
- var me = bus.fetch('ls/me')
46
+ var me = bus.get('ls/me')
46
47
  bus.log('connect: me is', me)
47
48
  if (!me.client) {
48
49
  // Create a client id if we have none yet.
@@ -51,19 +52,20 @@
51
52
  me.client = c || (Math.random().toString(36).substring(2)
52
53
  + Math.random().toString(36).substring(2)
53
54
  + Math.random().toString(36).substring(2))
54
- bus.save(me)
55
+ bus.set(me)
55
56
  }
56
57
 
57
58
  set_cookie('client', me.client)
58
59
  return {clientid: me.client}
59
60
  }
60
61
 
61
- function braid_http_mount (prefix, url) {
62
+ function http_mount (prefix, url) {
62
63
  var preprefix = prefix.slice(0,-1)
63
64
  var has_prefix = new RegExp('^' + preprefix)
64
65
  var is_absolute = /^https?:\/\//
65
- var client_fetched_keys = new bus.Set()
66
- //if (url[url.length-1]=='/') url = url.substr(0,url.length-1)
66
+ var subscriptions = {}
67
+ var put_counter = 0
68
+
67
69
  function add_prefix (key) {
68
70
  return is_absolute.test(key) ? key : preprefix + key }
69
71
  function rem_prefix (key) {
@@ -73,45 +75,146 @@
73
75
  function rem_prefixes (obj) {
74
76
  return bus.translate_keys(bus.clone(obj), rem_prefix) }
75
77
 
76
- bus(prefix).to_save = function (obj, t) {
77
- bus.save.fire(obj)
78
+ var puts = new Map()
79
+ function enqueue_put (url, body) {
80
+ var id = put_counter++
81
+ puts.set(id, {url: url, body: body, id: id})
82
+ send_put(id)
83
+ }
84
+ function send_put (id) {
85
+ try {
86
+ puts.get(id).status = 'sending'
87
+ braid_fetch(
88
+ puts.get(id).url,
89
+ {
90
+ method: 'put',
91
+ headers: {
92
+ 'content-type': 'application/json',
93
+ 'put-order': id,
94
+ },
95
+ body: puts.get(id).body
96
+ }
97
+ ).then(function (res) {
98
+ if (res.status !== 200)
99
+ console.error('Server gave error on PUT:',
100
+ e, 'for', puts.get(id).body)
101
+ puts.delete(id)
102
+ }).catch(function (e) {
103
+ console.error('Error on PUT, waiting...', puts.get(id).url)
104
+ puts.get(id).status = 'waiting'
105
+ })
106
+ } catch (e) {
107
+ console.error('Error on PUT, waiting...', puts.get(id).url)
108
+ puts.get(id).status = 'waiting'
109
+ }
110
+ }
111
+ function retry_put (id) {
112
+ setTimeout(function () {send_put(id)}, 1000)
113
+ }
114
+ function send_all_puts () {
115
+ for (var id of puts.keys())
116
+ if (puts.get(id).status === 'waiting') {
117
+ console.log('Sending waiting put', id)
118
+ send_put(id)
119
+ }
120
+ }
78
121
 
79
- // var x = {save: obj}
80
- // if (t.version) x.version = t.version
81
- // if (t.parents) x.parents = t.parents
82
- // if (t.patch) x.patch = t.patch
83
- // if (t.patch) x.save = rem_prefix(x.save.key)
122
+ bus(prefix).to_set = function (obj, t) {
123
+ bus.set.fire(obj)
84
124
 
85
- braid_fetch(url + rem_prefix(obj.key),
86
- {
87
- method: 'put',
88
- 'content-type': 'application/json',
89
- body: JSON.stringify(obj.val)
90
- })
125
+ var put = {
126
+ url: url + rem_prefix(obj.key),
127
+ body: JSON.stringify(obj.val)
128
+ }
129
+ if (t.version) put.version = t.version
130
+ if (t.parents) put.parents = t.parents
131
+ if (t.patch) put.patch = t.patch
132
+ var put_id = put_counter++
133
+ puts.set(put_id, put)
134
+ send_put(put_id)
91
135
  }
92
136
 
93
- bus(prefix).to_fetch = function (key, t) {
94
- try {
95
- braid_fetch(url + rem_prefix(key),
96
- {
97
- method: 'get',
98
- subscribe: true,
99
- headers: {accept: 'application/json'}
100
- }
101
- ).andThen( function (x) {
102
- t.return({
103
- key: key,
104
- val: add_prefixes(JSON.parse(x.body))
105
- })
106
- })
107
- } catch (e) {
108
- console.error('Braid-HTTP network error:', e)
137
+ bus(prefix).to_get = function (key, t) {
138
+ // Subscription can be in states:
139
+ // - connecting
140
+ // - connected
141
+ // - reconnect
142
+ // - reconnecting
143
+ // - aborted
144
+
145
+ // If we have an outstanding get running, then let's tell it to
146
+ // re-activate!
147
+ if (subscriptions[key]) {
148
+ // We should only be here if an existing subscription was
149
+ // aborted, but hasn't cleared yet.
150
+ console.assert(subscriptions[key].status === 'aborted',
151
+ 'Regetting a subscription of status '
152
+ + subscriptions[key].status)
153
+
154
+ console.trace('foo')
155
+
156
+ // Let's tell it to reconnect when it tries to clear!
157
+ subscriptions[key].status = 'reconnect'
158
+ }
159
+
160
+ // Otherwise, create a new subscription
161
+ else
162
+ subscribe (key, t)
163
+
164
+ function subscribe (key, t) {
165
+ var aborter = new AbortController(),
166
+ reconnect_attempts = 0
167
+
168
+ // Start the subscription!
169
+ braid_fetch(
170
+ // URL
171
+ url + rem_prefix(key),
172
+
173
+ // Options
174
+ {
175
+ method: 'get',
176
+ subscribe: true,
177
+ headers: {accept: 'application/json'},
178
+ signal: aborter.signal
179
+ }
180
+ ).andThen( function (x) {
181
+ // New update received!
182
+ if (subscriptions[key].status === 'connecting') {
183
+ console.log('%c[*] opened ' + key,
184
+ 'color: blue')
185
+ reconnect_attempts = 0
186
+ subscriptions[key].status = 'connected'
187
+ send_all_puts()
188
+ }
189
+
190
+ // Return the update
191
+ t.return({
192
+ key: key,
193
+ val: add_prefixes(JSON.parse(x.body))
194
+ })
195
+ }).catch( function (e) {
196
+ if (subscriptions[key].status === 'aborted') {
197
+ // Then this get is over and done with!
198
+ delete subscriptions[key]
199
+ return
200
+ }
201
+
202
+ // Reconnect!
203
+ setTimeout(function () { subscribe(key, t) },
204
+ reconnect_attempts > 0 ? 5000 : 1500)
205
+ subscriptions[key].status = 'reconnecting'
206
+ })
207
+
208
+ // Remember this subscription
209
+ subscriptions[key] = {
210
+ aborter: aborter,
211
+ status: 'connecting'
212
+ }
109
213
  }
110
- client_fetched_keys.add(key)
111
214
  }
112
215
  bus(prefix).to_forget = function (key) {
113
- // send({forget: key}),
114
- client_fetched_keys.delete(key)
216
+ subscriptions[key].status = 'aborted'
217
+ subscriptions[key].aborter.abort()
115
218
  }
116
219
  }
117
220
 
@@ -121,37 +224,30 @@
121
224
  function localstorage_client (prefix) {
122
225
  try { localStorage } catch (e) { return }
123
226
 
124
- // This doesn't yet trigger updates across multiple browser windows.
125
- // We can do that by adding a list of dirty keys and
227
+ // Sets are queued up, to store values with a delay, in batch
228
+ var sets_are_pending = false
229
+ var pending_sets = {}
126
230
 
127
- var bus = this
128
- bus.log(this)
129
-
130
- // Fetch returns the value immediately in a save
131
- // Saves are queued up, to store values with a delay, in batch
132
- var saves_are_pending = false
133
- var pending_saves = {}
134
-
135
- function save_the_pending_saves() {
136
- bus.log('localstore: saving', pending_saves)
137
- for (var k in pending_saves)
138
- localStorage.setItem(k, JSON.stringify(pending_saves[k]))
139
- saves_are_pending = false
231
+ function set_the_pending_sets() {
232
+ bus.log('localstore: saving', pending_sets)
233
+ for (var k in pending_sets)
234
+ localStorage.setItem(k, JSON.stringify(pending_sets[k]))
235
+ sets_are_pending = false
140
236
  }
141
237
 
142
- bus(prefix).to_fetch = function (key) {
238
+ bus(prefix).to_get = function (key) {
143
239
  var result = localStorage.getItem(key)
144
240
  return result ? JSON.parse(result) : {key: key}
145
241
  }
146
- bus(prefix).to_save = function (obj) {
242
+ bus(prefix).to_set = function (obj) {
147
243
  // Do I need to make this recurse into the object?
148
- bus.log('localStore: on_save:', obj.key)
149
- pending_saves[obj.key] = obj
150
- if (!saves_are_pending) {
151
- setTimeout(save_the_pending_saves, 50)
152
- saves_are_pending = true
244
+ bus.log('localStore: on_set:', obj.key)
245
+ pending_sets[obj.key] = obj
246
+ if (!sets_are_pending) {
247
+ setTimeout(set_the_pending_sets, 50)
248
+ sets_are_pending = true
153
249
  }
154
- bus.save.fire(obj)
250
+ bus.set.fire(obj)
155
251
  return obj
156
252
  }
157
253
  bus(prefix).to_delete = function (key) { localStorage.removeItem(key) }
@@ -160,6 +256,7 @@
160
256
  // Hm... this update stuff doesn't seem to work on file:/// urls in chrome
161
257
  function update (event) {
162
258
  bus.log('Got a localstorage update', event)
259
+ bus.dirty(event.key)
163
260
  //this.get(event.key.substr('statebus '.length))
164
261
  }
165
262
  if (window.addEventListener) window.addEventListener("storage", update, false)
@@ -184,39 +281,16 @@
184
281
  data = (data && JSON.parse(data)) || {key : key}
185
282
  // Then I would need to:
186
283
  // - Change the key prefix
187
- // - Save this into the cache
284
+ // - Set this into the cache
188
285
 
189
- bus(prefix).to_save = function (obj) {
286
+ bus(prefix).to_set = function (obj) {
190
287
  window.history.replaceState(
191
288
  '',
192
289
  '',
193
290
  document.location.origin
194
291
  + document.location.pathname
195
292
  + escape('?'+key+'='+JSON.stringify(obj)))
196
- bus.save.fire(obj)
197
- }
198
- }
199
-
200
- function live_reload_from (prefix) {
201
- if (!window.live_reload_initialized) {
202
- var first_time = true
203
- this(function () {
204
- var re = new RegExp(".*/" + prefix + "/(.*)")
205
- var file = window.location.href.match(re)[1]
206
- var code = bus.fetch('/code/invisible.college/' + file).code
207
- if (!code) return
208
- if (first_time) {first_time = false; return}
209
- var old_scroll_position = window.pageYOffset
210
- document.body.innerHTML = code
211
- var i = 0
212
- var d = 100
213
- var interval = setInterval(function () {
214
- if (i > 500) clearInterval(interval)
215
- i += d
216
- window.scrollTo(0, old_scroll_position)
217
- }, d)
218
- })
219
- window.live_reload_initialized = true
293
+ bus.set.fire(obj)
220
294
  }
221
295
  }
222
296
 
@@ -224,30 +298,40 @@
224
298
  // ****************
225
299
  // Wrapper for React Components
226
300
 
301
+ function react_version () {
302
+ if (!window.React) return undefined;
303
+ return Number(window.React.version.split('.')[0])
304
+ }
305
+
306
+ // Newer React requires createReactClass as a separate libary
307
+ if (window.React && !React.createClass && window.createReactClass)
308
+ React.createClass = createReactClass
309
+
227
310
  // XXX Currently assumes there's a statebus named "bus" in global
228
311
  // XXX scope.
229
312
 
230
313
  var components = {} // Indexed by 'component/0', 'component/1', etc.
231
314
  var components_count = 0
232
315
  var dirty_components = {}
233
- function React_View(component) {
316
+ function create_react_class(component) {
234
317
  function wrap(name, new_func) {
235
318
  var old_func = component[name]
236
319
  component[name] = function wrapper () { return new_func.bind(this)(old_func) }
237
320
  }
238
321
 
239
322
  // Register the component's basic info
240
- wrap('componentWillMount', function new_cwm (orig_func) {
241
- if (component.displayName === undefined)
242
- throw 'Component needs a displayName'
243
- this.name = component.displayName.toLowerCase().replace(' ', '_')
323
+ wrap((react_version() >= 16 ? 'UNSAFE_' : '') + 'componentWillMount',
324
+ function new_cwm (orig_func) {
325
+ // if (component.displayName === undefined)
326
+ // throw 'Component needs a displayName'
327
+ //this.name = component.displayName.toLowerCase().replace(' ', '_')
244
328
  this.key = 'component/' + components_count++
245
329
  components[this.key] = this
246
330
 
247
331
  function add_shortcut (obj, shortcut_name, to_key) {
248
332
  delete obj[shortcut_name]
249
333
  Object.defineProperty(obj, shortcut_name, {
250
- get: function () { return bus.fetch(to_key) },
334
+ get: function () { return bus.get(to_key) },
251
335
  configurable: true })
252
336
  }
253
337
  add_shortcut(this, 'local', this.key)
@@ -268,7 +352,7 @@
268
352
  && typeof this.props[k] === 'object'
269
353
  && this.props[k].key)
270
354
 
271
- bus.fetch(this.props[k].key)
355
+ bus.get(this.props[k].key)
272
356
 
273
357
  // Call the renderer!
274
358
  return orig_render.apply(this, arguments)
@@ -339,17 +423,12 @@
339
423
  props['data-key'] = props.key
340
424
  props['data-widget'] = component.displayName
341
425
 
342
- return (React.version >= '0.12.'
343
- ? React.createElement(react_class, props, children)
344
- : react_class(props, children))
426
+ return React.createElement(react_class, props, children)
345
427
  }
346
- // Give it the same prototype as the original class so that it
347
- // passes React.isValidClass() inspection
348
- result.prototype = react_class.prototype
428
+ Object.defineProperty(result, 'name',
429
+ {value: component.displayName, writable: false})
349
430
  return result
350
431
  }
351
- window.React_View = React_View
352
- if (window.statebus) window.statebus.create_react_class = window.statebus.createReactClass = React_View
353
432
 
354
433
  // *****************
355
434
  // Re-rendering react components
@@ -384,7 +463,7 @@
384
463
 
385
464
  function make_client_statebus_maker () {
386
465
  var extra_stuff = ['localstorage_client make_websocket client_creds',
387
- 'url_store components live_reload_from'].join(' ').split(' ')
466
+ 'url_store components'].join(' ').split(' ')
388
467
  if (window.statebus) {
389
468
  var orig_statebus = statebus
390
469
  window.statebus = function make_client_bus () {
@@ -405,17 +484,17 @@
405
484
  if (statebus_dir) statebus_dir = statebus_dir[1] + '/'
406
485
  else statebus_dir = ''
407
486
 
408
- var js_urls = {
409
- react: statebus_dir + 'extras/react.js',
410
- sockjs: statebus_dir + 'extras/sockjs.js',
411
- coffee: statebus_dir + 'extras/coffee.js',
412
- statebus: statebus_dir + 'statebus.js'
413
- }
414
- if (statebus_dir == 'https://stateb.us/')
415
- js_urls.statebus = statebus_dir + 'statebus4.js'
487
+ // var js_urls = {
488
+ // react: statebus_dir + 'extras/react.js',
489
+ // sockjs: statebus_dir + 'extras/sockjs.js',
490
+ // coffee: statebus_dir + 'extras/coffee.js',
491
+ // statebus: statebus_dir + 'statebus.js'
492
+ // }
493
+ // if (statebus_dir == 'https://stateb.us/')
494
+ // js_urls.statebus = statebus_dir + 'statebus4.js'
416
495
 
417
- for (var name in js_urls)
418
- document.write('<script src="' + js_urls[name] + '" charset="utf-8"></script>')
496
+ // for (var name in js_urls)
497
+ // document.write('<script src="' + js_urls[name] + '" charset="utf-8"></script>')
419
498
 
420
499
  document.addEventListener('DOMContentLoaded', scripts_ready, false)
421
500
  }
@@ -426,56 +505,52 @@
426
505
  function clientjs_option (option_name) {
427
506
  // This function must be copy/paste synchronized with statebus.js. Be
428
507
  // sure to clone all edits there.
429
- var script_elem = (
430
- document.querySelector('script[src*="/client"][src$=".js"]') ||
431
- document.querySelector('script[src^="client"][src$=".js"]'))
508
+ var script_elem = document.querySelector('script[src$="statebus/client.js"]')
432
509
  return script_elem && script_elem.getAttribute(option_name)
433
510
  }
434
511
  var loaded_from_file_url = window.location.href.match(/^file:\/\//)
435
- window.statebus_server = window.statebus_server || clientjs_option('server') ||
436
- (loaded_from_file_url ? 'https://stateb.us:3006' : '/')
437
- window.statebus_backdoor = window.statebus_backdoor || clientjs_option('backdoor')
512
+
513
+ // Todo: remove this global
514
+ window.statebus_server = clientjs_option('server')
438
515
  var react_render
439
516
  function scripts_ready () {
440
- react_render = React.version >= '0.14.' ? ReactDOM.render : React.render
441
- make_client_statebus_maker()
517
+ react_render = ReactDOM.render
518
+ // make_client_statebus_maker()
442
519
  window.bus = window.statebus()
443
520
  bus.label = 'bus'
444
- if (clientjs_option('braid_mode'))
445
- bus.state = bus.braid_proxy()
446
- else
447
- window.sb = bus.sb
448
- statebus.widget = React_View
449
- statebus.create_react_class = React_View
450
- statebus.createReactClass = React_View
451
-
452
- improve_react()
453
- window.ignore_flashbacks = false
454
- if (statebus_server !== 'none') {
455
- if (clientjs_option('braid_mode')) {
456
- console.log('Using Braid-HTTP!')
457
- braid_http_mount ('/*', statebus_server)
458
- } else {
459
- bus.net_mount ('/*', statebus_server)
460
- }
461
- }
462
521
 
463
- if (window.statebus_backdoor) {
464
- window.master = statebus()
465
- master.net_mount('*', statebus_backdoor)
466
- }
522
+ statebus.create_react_class = create_react_class
523
+ statebus.createReactClass = create_react_class
524
+
525
+ // improve_react()
526
+ statebus.ignore_flashbacks = false
527
+ bus.libs = {}
528
+ bus.libs.http_out = http_mount
529
+ bus.libs.react_class = create_react_class
530
+ bus.libs.input = make_better_input_17()
531
+ bus.libs.localstorage = localstorage_client
532
+
533
+ // if (statebus_server !== 'none') {
534
+ // if (clientjs_option('braid_mode')) {
535
+ // console.log('Using Braid-HTTP!')
536
+ // http_mount ('/*', statebus_server)
537
+ // } else {
538
+ // bus.ws_mount ('/*', statebus_server)
539
+ // }
540
+ // }
541
+
467
542
  bus.net_automount()
468
543
 
469
544
  // This /new/* code is deprecated
470
545
  if (!clientjs_option('braid_mode')) {
471
- bus('/new/*').to_save = function (o) {
546
+ bus('/new/*').to_set = function (o) {
472
547
  if (o.key.split('/').length > 3) return
473
548
 
474
549
  var old_key = o.key
475
550
  o.key = old_key + '/' + Math.random().toString(36).substring(2,12)
476
551
  statebus.cache[o.key] = o
477
552
  delete statebus.cache[old_key]
478
- bus.save(o)
553
+ bus.set(o)
479
554
  }
480
555
  }
481
556
 
@@ -485,6 +560,17 @@
485
560
  statebus.load_client_code = load_client_code
486
561
  statebus.load_widgets = load_widgets
487
562
 
563
+ if (clientjs_option('globals')) {
564
+ // Setup globals
565
+ var globals = ['get', 'set', 'state']
566
+
567
+ for (var i=0; i<globals.length; i++) {
568
+ console.log('globalizing', globals[i], 'as',
569
+ eval('bus.' + globals[i]))
570
+ window[globals[i]] = eval('bus.' + globals[i])
571
+ }
572
+ }
573
+
488
574
  document.addEventListener('DOMContentLoaded', function () {
489
575
  if (window.statebus_ready)
490
576
  for (var i=0; i<statebus_ready.length; i++)
@@ -604,7 +690,7 @@
604
690
  for (var i=0; i<arguments.length; i++) {
605
691
  args.push(arguments[i])
606
692
  if (arguments[i].state)
607
- args[i].src = 'data:;base64,' + fetch(args[i].state)._
693
+ args[i].src = 'data:;base64,' + bus.get(args[i].state)._
608
694
  }
609
695
  return og_img.apply(this, args)
610
696
  }
@@ -636,12 +722,38 @@
636
722
  }
637
723
 
638
724
 
725
+ // This one is for react v17+
726
+ function make_better_input_17 () {
727
+ return createReactClass({
728
+ getInitialState: function() {
729
+ return {value: this.props.value}
730
+ },
731
+ UNSAVE_componentWillReceiveProps: function(new_props) {
732
+ this.setState({value: new_props.value})
733
+ },
734
+ onChange: function(e) {
735
+ this.props.onChange && this.props.onChange(e)
736
+ if (this.props.value)
737
+ this.setState({value: e.target.value})
738
+ },
739
+ render: function() {
740
+ var new_props = {}
741
+ for (var k in this.props)
742
+ if (this.props.hasOwnProperty(k))
743
+ new_props[k] = this.props[k]
744
+ if (this.state.value) new_props.value = this.state.value
745
+ new_props.onChange = this.onChange
746
+ return React.createElement('input', new_props)
747
+ }
748
+ })
749
+ }
750
+
639
751
  // Load the components
640
752
  var users_widgets = {}
641
753
  function make_component(name, func) {
642
754
  // Define the component
643
755
 
644
- window[name] = users_widgets[name] = window.React_View({
756
+ window[name] = users_widgets[name] = statebus.create_react_class({
645
757
  displayName: name,
646
758
  render: function () {
647
759
  var args = []