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.
- package/index.js +253 -168
- 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
|
-
|
|
765
|
-
|
|
770
|
+
try {
|
|
771
|
+
var initial_connect_done
|
|
772
|
+
var initial_connect_promise = new Promise(done => initial_connect_done = done)
|
|
766
773
|
|
|
767
|
-
|
|
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
|
-
|
|
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
|
-
|
|
792
|
-
if (freed || closed) return
|
|
794
|
+
|
|
795
|
+
async function send_out(stuff) {
|
|
796
|
+
if (freed || closed) return
|
|
793
797
|
|
|
794
|
-
|
|
798
|
+
console.log(`send_out ${url} ${JSON.stringify(stuff, null, 4).slice(0, 1000)}`)
|
|
795
799
|
|
|
796
|
-
|
|
797
|
-
|
|
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
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
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
|
-
|
|
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
|
-
|
|
824
|
-
}
|
|
822
|
+
console.log(`[find_fork_point] url: ${url}`)
|
|
825
823
|
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
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
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
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
|
-
|
|
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
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
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
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
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
|
-
|
|
855
|
-
|
|
859
|
+
await find_fork_point()
|
|
860
|
+
if (freed || closed) return
|
|
856
861
|
|
|
857
|
-
|
|
858
|
-
|
|
862
|
+
await send_new_stuff()
|
|
863
|
+
if (freed || closed) return
|
|
859
864
|
|
|
860
|
-
|
|
861
|
-
|
|
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
|
-
|
|
877
|
-
if (freed || closed) return
|
|
879
|
+
if (freed || closed) return
|
|
878
880
|
|
|
879
|
-
|
|
881
|
+
if (res.status < 200 || res.status >= 300) return retry(new Error(`unexpected status: ${res.status}`))
|
|
880
882
|
|
|
881
|
-
|
|
882
|
-
|
|
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
|
-
|
|
885
|
-
|
|
886
|
+
console.log(`connected to ${url}`)
|
|
887
|
+
console.log(` editable = ${res.headers.get('editable')}`)
|
|
886
888
|
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
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
|
-
|
|
896
|
-
|
|
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
|
-
|
|
899
|
-
|
|
900
|
-
|
|
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
|
-
|
|
903
|
-
|
|
906
|
+
await wait_on(braid_text.put(url, { ...update, peer: self.peer, merge_type: 'dt' }))
|
|
907
|
+
if (freed || closed) return
|
|
904
908
|
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
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
|
-
|
|
911
|
-
|
|
914
|
+
self.signal_file_needs_writing()
|
|
915
|
+
}, retry)
|
|
912
916
|
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
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
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
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
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
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) {
|