braidfs 0.0.96 → 0.0.98

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.
Files changed (2) hide show
  1. package/index.js +253 -168
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -13,7 +13,9 @@ var trash = `${braidfs_config_dir}/trash`
13
13
  var temp_folder = `${braidfs_config_dir}/temp`
14
14
 
15
15
  var config = null,
16
- watcher_misses = 0
16
+ watcher_misses = 0,
17
+ reconnect_rate_limiter = new ReconnectRateLimiter(() =>
18
+ config?.reconnect_delay_ms ?? 3000)
17
19
 
18
20
  if (require('fs').existsSync(sync_base)) {
19
21
  try {
@@ -39,6 +41,7 @@ if (require('fs').existsSync(sync_base)) {
39
41
  cookies: { 'example.com': 'secret_pass' },
40
42
  port: 45678,
41
43
  scan_interval_ms: 1000 * 20,
44
+ reconnect_delay_ms: 1000 * 3
42
45
  }
43
46
  require('fs').mkdirSync(braidfs_config_dir, { recursive: true })
44
47
  require('fs').writeFileSync(braidfs_config_file, JSON.stringify(config, null, 4))
@@ -732,12 +735,14 @@ async function sync_url(url) {
732
735
  async function connect() {
733
736
  if (freed) return
734
737
  if (last_connect_timer) return
738
+ console.log(`connecting to ${url}`)
735
739
 
736
740
  var closed = false
737
741
  var prev_disconnect = self.disconnect
738
742
  self.disconnect = async () => {
739
743
  if (closed) return
740
744
  closed = true
745
+ reconnect_rate_limiter.on_diss(url)
741
746
  for (var a of aborts) a.abort()
742
747
  aborts.clear()
743
748
  if (braid_text_get_options) await braid_text.forget(url, braid_text_get_options)
@@ -755,112 +760,110 @@ async function sync_url(url) {
755
760
  console.log(`reconnecting in ${waitTime}s: ${url} after error: ${e}`)
756
761
  last_connect_timer = setTimeout(async () => {
757
762
  await p
763
+ await reconnect_rate_limiter.get_turn(url)
758
764
  last_connect_timer = null
759
765
  connect()
760
766
  }, waitTime * 1000)
761
767
  waitTime = Math.min(waitTime + 1, 3)
762
768
  }
763
769
 
764
- var initial_connect_done
765
- var initial_connect_promise = new Promise(done => initial_connect_done = done)
770
+ try {
771
+ var initial_connect_done
772
+ var initial_connect_promise = new Promise(done => initial_connect_done = done)
766
773
 
767
- async function my_fetch(params) {
768
- if (freed || closed) return
769
- try {
770
- var a = new AbortController()
771
- aborts.add(a)
772
- return await braid_fetch(url, {
773
- signal: a.signal,
774
- headers: {
775
- "Merge-Type": "dt",
776
- "Content-Type": 'text/plain',
777
- ...(x => x && {Cookie: x})(config.cookies?.[new URL(url).hostname])
778
- },
779
- retry: { retryRes: r => r.status !== 401 && r.status !== 403 },
780
- ...params
781
- })
782
- } catch (e) {
783
- if (freed || closed) return
784
- if (e?.name !== "AbortError") console.log(e)
785
- } finally {
774
+ async function my_fetch(params) {
786
775
  if (freed || closed) return
787
- aborts.delete(a)
776
+ try {
777
+ var a = new AbortController()
778
+ aborts.add(a)
779
+ return await braid_fetch(url, {
780
+ signal: a.signal,
781
+ headers: {
782
+ "Merge-Type": "dt",
783
+ "Content-Type": 'text/plain',
784
+ ...(x => x && {Cookie: x})(config.cookies?.[new URL(url).hostname])
785
+ },
786
+ ...params
787
+ })
788
+ } catch (e) {
789
+ if (freed || closed) return
790
+ aborts.delete(a)
791
+ throw e
792
+ }
788
793
  }
789
- }
790
-
791
- async function send_out(stuff) {
792
- if (freed || closed) return
794
+
795
+ async function send_out(stuff) {
796
+ if (freed || closed) return
793
797
 
794
- console.log(`send_out ${url} ${JSON.stringify(stuff, null, 4).slice(0, 1000)}`)
798
+ console.log(`send_out ${url} ${JSON.stringify(stuff, null, 4).slice(0, 1000)}`)
795
799
 
796
- var r = await my_fetch({ method: "PUT", ...stuff })
797
- if (freed || closed) return
800
+ var r = await my_fetch({ method: "PUT", ...stuff,
801
+ retry: { retryRes: r => r.status !== 401 && r.status !== 403 }})
802
+ if (freed || closed) return
803
+
804
+ // the server has acknowledged this version,
805
+ // so add it to the fork point
806
+ if (r.ok) self.update_fork_point(stuff.version[0], stuff.parents)
798
807
 
799
- // the server has acknowledged this version,
800
- // so add it to the fork point
801
- if (r.ok) self.update_fork_point(stuff.version[0], stuff.parents)
802
-
803
- // if we're not authorized,
804
- if (r.status == 401 || r.status == 403) {
805
- // and it's one of our versions (a local edit),
806
- if (self.peer === braid_text.decode_version(stuff.version[0])[0]) {
807
- // then revert it
808
- console.log(`access denied: reverting local edits`)
809
- unsync_url(url)
810
- sync_url(url)
808
+ // if we're not authorized,
809
+ if (r.status == 401 || r.status == 403) {
810
+ // and it's one of our versions (a local edit),
811
+ if (self.peer === braid_text.decode_version(stuff.version[0])[0]) {
812
+ // then revert it
813
+ console.log(`access denied: reverting local edits`)
814
+ unsync_url(url)
815
+ sync_url(url)
816
+ }
811
817
  }
812
818
  }
813
- }
814
819
 
815
- async function find_fork_point() {
816
- if (freed || closed) return
817
- console.log(`[find_fork_point] url: ${url}`)
818
-
819
- // see if remote has the fork point
820
- if (self.fork_point) {
821
- var r = await my_fetch({ method: "HEAD", version: self.fork_point })
820
+ async function find_fork_point() {
822
821
  if (freed || closed) return
823
- if (r.ok) return console.log(`[find_fork_point] it has our latest fork point, hooray!`)
824
- }
822
+ console.log(`[find_fork_point] url: ${url}`)
825
823
 
826
- // otherwise let's binary search for new fork point..
827
- var bytes = resource.doc.toBytes()
828
- var [_, events, __] = braid_text.dt_parse([...bytes])
829
- events = events.map(x => x.join('-'))
824
+ // see if remote has the fork point
825
+ if (self.fork_point) {
826
+ var r = await my_fetch({ method: "HEAD", version: self.fork_point })
827
+ if (freed || closed) return
828
+ if (r.ok) return console.log(`[find_fork_point] it has our latest fork point, hooray!`)
829
+ }
830
830
 
831
- var min = -1
832
- var max = events.length
833
- self.fork_point = []
834
- while (min + 1 < max) {
835
- var i = Math.floor((min + max)/2)
836
- var version = [events[i]]
831
+ // otherwise let's binary search for new fork point..
832
+ var bytes = resource.doc.toBytes()
833
+ var [_, events, __] = braid_text.dt_parse([...bytes])
834
+ events = events.map(x => x.join('-'))
837
835
 
838
- console.log(`min=${min}, max=${max}, i=${i}, version=${version}`)
836
+ var min = -1
837
+ var max = events.length
838
+ self.fork_point = []
839
+ while (min + 1 < max) {
840
+ var i = Math.floor((min + max)/2)
841
+ var version = [events[i]]
839
842
 
840
- var st = Date.now()
841
- var r = await my_fetch({ method: "HEAD", version })
842
- if (freed || closed) return
843
- console.log(`fetched in ${Date.now() - st}`)
843
+ console.log(`min=${min}, max=${max}, i=${i}, version=${version}`)
844
+
845
+ var st = Date.now()
846
+ var r = await my_fetch({ method: "HEAD", version })
847
+ if (freed || closed) return
848
+ console.log(`fetched in ${Date.now() - st}`)
844
849
 
845
- if (r.ok) {
846
- min = i
847
- self.fork_point = version
848
- } else max = i
850
+ if (r.ok) {
851
+ min = i
852
+ self.fork_point = version
853
+ } else max = i
854
+ }
855
+ console.log(`[find_fork_point] settled on: ${JSON.stringify(self.fork_point)}`)
856
+ self.signal_file_needs_writing(true)
849
857
  }
850
- console.log(`[find_fork_point] settled on: ${JSON.stringify(self.fork_point)}`)
851
- self.signal_file_needs_writing(true)
852
- }
853
858
 
854
- await find_fork_point()
855
- if (freed || closed) return
859
+ await find_fork_point()
860
+ if (freed || closed) return
856
861
 
857
- await send_new_stuff()
858
- if (freed || closed) return
862
+ await send_new_stuff()
863
+ if (freed || closed) return
859
864
 
860
- console.log(`connecting to ${url}`)
861
- let a = new AbortController()
862
- aborts.add(a)
863
- try {
865
+ let a = new AbortController()
866
+ aborts.add(a)
864
867
  var res = await braid_fetch(url, {
865
868
  signal: a.signal,
866
869
  headers: {
@@ -873,106 +876,108 @@ async function sync_url(url) {
873
876
  parents: self.fork_point,
874
877
  peer: self.peer
875
878
  })
876
- } catch (e) { return retry(e) }
877
- if (freed || closed) return
879
+ if (freed || closed) return
878
880
 
879
- if (res.status < 200 || res.status >= 300) return retry(new Error(`unexpected status: ${res.status}`))
881
+ if (res.status < 200 || res.status >= 300) return retry(new Error(`unexpected status: ${res.status}`))
880
882
 
881
- if (res.status !== 209)
882
- return log_error(`Can't sync ${url} -- got bad response ${res.status} from server (expected 209)`)
883
+ if (res.status !== 209)
884
+ return log_error(`Can't sync ${url} -- got bad response ${res.status} from server (expected 209)`)
883
885
 
884
- console.log(`connected to ${url}`)
885
- console.log(` editable = ${res.headers.get('editable')}`)
886
+ console.log(`connected to ${url}`)
887
+ console.log(` editable = ${res.headers.get('editable')}`)
886
888
 
887
- self.file_read_only = res.headers.get('editable') === 'false'
888
- self.signal_file_needs_writing()
889
-
890
- initial_connect_done()
891
- res.subscribe(async update => {
892
- if (freed || closed) return
893
- console.log(`got external update about ${url}`)
889
+ reconnect_rate_limiter.on_conn(url)
890
+
891
+ self.file_read_only = res.headers.get('editable') === 'false'
892
+ self.signal_file_needs_writing()
893
+
894
+ initial_connect_done()
895
+ res.subscribe(async update => {
896
+ if (freed || closed) return
897
+ console.log(`got external update about ${url}`)
894
898
 
895
- if (update.body) update.body = update.body_text
896
- if (update.patches) for (let p of update.patches) p.content = p.content_text
899
+ if (update.body) update.body = update.body_text
900
+ if (update.patches) for (let p of update.patches) p.content = p.content_text
897
901
 
898
- // console.log(`update: ${JSON.stringify(update, null, 4)}`)
899
- if (update.version.length === 0) return
900
- if (update.version.length !== 1) throw 'unexpected'
902
+ // console.log(`update: ${JSON.stringify(update, null, 4)}`)
903
+ if (update.version.length === 0) return
904
+ if (update.version.length !== 1) throw 'unexpected'
901
905
 
902
- await wait_on(braid_text.put(url, { ...update, peer: self.peer, merge_type: 'dt' }))
903
- if (freed || closed) return
906
+ await wait_on(braid_text.put(url, { ...update, peer: self.peer, merge_type: 'dt' }))
907
+ if (freed || closed) return
904
908
 
905
- // the server is giving us this version,
906
- // so it must have it,
907
- // so let's add it to our fork point
908
- self.update_fork_point(update.version[0], update.parents)
909
+ // the server is giving us this version,
910
+ // so it must have it,
911
+ // so let's add it to our fork point
912
+ self.update_fork_point(update.version[0], update.parents)
909
913
 
910
- self.signal_file_needs_writing()
911
- }, retry)
914
+ self.signal_file_needs_writing()
915
+ }, retry)
912
916
 
913
- // send it stuff we have but it doesn't't
914
- async function send_new_stuff() {
915
- if (freed || closed) return
916
- var q = []
917
- var in_flight = new Map()
918
- var max_in_flight = 10
919
- var send_pump_lock = 0
920
-
921
- async function send_pump() {
922
- send_pump_lock++
923
- if (send_pump_lock > 1) return
924
- try {
925
- if (freed || closed) return
926
- if (in_flight.size >= max_in_flight) return
927
- if (!q.length) {
928
- var frontier = self.fork_point
929
- for (var u of in_flight.values())
930
- frontier = extend_frontier(frontier, u.version[0], u.parents)
931
-
932
- var options = {
933
- parents: frontier,
934
- merge_type: 'dt',
935
- peer: self.peer,
936
- subscribe: u => u.version.length && q.push(u)
917
+ // send it stuff we have but it doesn't't
918
+ async function send_new_stuff() {
919
+ if (freed || closed) return
920
+ var q = []
921
+ var in_flight = new Map()
922
+ var max_in_flight = 10
923
+ var send_pump_lock = 0
924
+
925
+ async function send_pump() {
926
+ send_pump_lock++
927
+ if (send_pump_lock > 1) return
928
+ try {
929
+ if (freed || closed) return
930
+ if (in_flight.size >= max_in_flight) return
931
+ if (!q.length) {
932
+ var frontier = self.fork_point
933
+ for (var u of in_flight.values())
934
+ frontier = extend_frontier(frontier, u.version[0], u.parents)
935
+
936
+ var options = {
937
+ parents: frontier,
938
+ merge_type: 'dt',
939
+ peer: self.peer,
940
+ subscribe: u => u.version.length && q.push(u)
941
+ }
942
+ await braid_text.get(url, options)
943
+ await braid_text.forget(url, options)
937
944
  }
938
- await braid_text.get(url, options)
939
- await braid_text.forget(url, options)
940
- }
941
- while (q.length && in_flight.size < max_in_flight) {
942
- let u = q.shift()
943
- in_flight.set(u.version[0], u);
944
- (async () => {
945
- await initial_connect_promise
946
- if (freed || closed) return
947
- await send_out({...u, peer: self.peer})
948
- if (freed || closed) return
949
- in_flight.delete(u.version[0])
950
- setTimeout(send_pump, 0)
951
- })()
945
+ while (q.length && in_flight.size < max_in_flight) {
946
+ let u = q.shift()
947
+ in_flight.set(u.version[0], u);
948
+ (async () => {
949
+ await initial_connect_promise
950
+ if (freed || closed) return
951
+ await send_out({...u, peer: self.peer})
952
+ if (freed || closed) return
953
+ in_flight.delete(u.version[0])
954
+ setTimeout(send_pump, 0)
955
+ })()
956
+ }
957
+ } finally {
958
+ var retry = send_pump_lock > 1
959
+ send_pump_lock = 0
960
+ if (retry) setTimeout(send_pump, 0)
952
961
  }
953
- } finally {
954
- var retry = send_pump_lock > 1
955
- send_pump_lock = 0
956
- if (retry) setTimeout(send_pump, 0)
957
962
  }
958
- }
959
963
 
960
- var initial_stuff = true
961
- await wait_on(braid_text.get(url, braid_text_get_options = {
962
- parents: self.fork_point,
963
- merge_type: 'dt',
964
- peer: self.peer,
965
- subscribe: async (u) => {
966
- if (freed || closed) return
967
- if (u.version.length) {
968
- self.signal_file_needs_writing()
969
- if (initial_stuff || in_flight.size < max_in_flight) q.push(u)
970
- send_pump()
971
- }
972
- },
973
- }))
974
- initial_stuff = false
975
- }
964
+ var initial_stuff = true
965
+ await wait_on(braid_text.get(url, braid_text_get_options = {
966
+ parents: self.fork_point,
967
+ merge_type: 'dt',
968
+ peer: self.peer,
969
+ subscribe: async (u) => {
970
+ if (freed || closed) return
971
+ if (u.version.length) {
972
+ self.signal_file_needs_writing()
973
+ if (initial_stuff || in_flight.size < max_in_flight) q.push(u)
974
+ send_pump()
975
+ }
976
+ },
977
+ }))
978
+ initial_stuff = false
979
+ }
980
+ } catch (e) { return retry(e) }
976
981
  }
977
982
 
978
983
  // for config and errors file, listen for web changes
@@ -1007,6 +1012,86 @@ async function ensure_path(path) {
1007
1012
  }
1008
1013
  }
1009
1014
 
1015
+ function ReconnectRateLimiter(get_wait_time) {
1016
+ var self = {}
1017
+
1018
+ self.conns = new Map() // Map<host, Set<url>>
1019
+ self.host_to_q = new Map() // Map<host, Array<resolve>>
1020
+ self.qs = [] // Array<{host, turns: Array<resolve>, last_turn}>
1021
+ self.last_turn = 0
1022
+ self.timer = null
1023
+
1024
+ function process() {
1025
+ if (self.timer) clearTimeout(self.timer)
1026
+ self.timer = null
1027
+
1028
+ if (!self.qs.length) return
1029
+ var now = Date.now()
1030
+ var my_last_turn = () => self.conns.size === 0 ? self.last_turn : self.qs[0].last_turn
1031
+
1032
+ while (self.qs.length && now >= my_last_turn() + get_wait_time()) {
1033
+ var x = self.qs.shift()
1034
+ if (!x.turns.length) {
1035
+ self.host_to_q.delete(x.host)
1036
+ continue
1037
+ }
1038
+
1039
+ x.turns.shift()()
1040
+ x.last_turn = self.last_turn = now
1041
+ self.qs.push(x)
1042
+ }
1043
+
1044
+ if (self.qs.length)
1045
+ self.timer = setTimeout(process, Math.max(0,
1046
+ my_last_turn() + get_wait_time() - now))
1047
+ }
1048
+
1049
+ self.get_turn = async (url) => {
1050
+ var host = new URL(url).host
1051
+
1052
+ // If host has connections, give turn immediately
1053
+ if (self.conns.has(host)) return
1054
+
1055
+ console.log(`throttling reconn to ${url} (no conns yet to ${self.conns.size ? host : 'anything'})`)
1056
+
1057
+ if (!self.host_to_q.has(host)) {
1058
+ var turns = []
1059
+ self.host_to_q.set(host, turns)
1060
+ self.qs.unshift({host, turns, last_turn: 0})
1061
+ }
1062
+ var p = new Promise(resolve => self.host_to_q.get(host).push(resolve))
1063
+ process()
1064
+ await p
1065
+ }
1066
+
1067
+ self.on_conn = url => {
1068
+ var host = new URL(url).host
1069
+ if (!self.conns.has(host))
1070
+ self.conns.set(host, new Set())
1071
+ self.conns.get(host).add(url)
1072
+
1073
+ // If there are turns waiting for this host, resolve them all immediately
1074
+ var turns = self.host_to_q.get(host)
1075
+ if (turns) {
1076
+ for (var turn of turns) turn()
1077
+ turns.splice(0, turns.length)
1078
+ }
1079
+
1080
+ process()
1081
+ }
1082
+
1083
+ self.on_diss = url => {
1084
+ var host = new URL(url).host
1085
+ var urls = self.conns.get(host)
1086
+ if (urls) {
1087
+ urls.delete(url)
1088
+ if (urls.size === 0) self.conns.delete(host)
1089
+ }
1090
+ }
1091
+
1092
+ return self
1093
+ }
1094
+
1010
1095
  ////////////////////////////////
1011
1096
 
1012
1097
  function normalize_url(url) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "braidfs",
3
- "version": "0.0.96",
3
+ "version": "0.0.98",
4
4
  "description": "braid technology synchronizing files and webpages",
5
5
  "author": "Braid Working Group",
6
6
  "repository": "braid-org/braidfs",