braid-blob 0.0.49 → 0.0.51
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 +321 -355
- package/package.json +1 -1
- package/test/tests.js +6 -17
package/index.js
CHANGED
|
@@ -14,153 +14,214 @@ function create_braid_blob() {
|
|
|
14
14
|
|
|
15
15
|
var temp_folder = null // will be set in init
|
|
16
16
|
|
|
17
|
-
braid_blob.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
braid_blob.init = () => init_p
|
|
21
|
-
await braid_blob.init()
|
|
17
|
+
braid_blob.sync = (a, b, options = {}) => {
|
|
18
|
+
options = normalize_options(options)
|
|
19
|
+
if (!options.peer) options.peer = Math.random().toString(36).slice(2)
|
|
22
20
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
// Support for same-type params removed for now,
|
|
22
|
+
// since it is unused, unoptimized,
|
|
23
|
+
// and not as well battle tested
|
|
24
|
+
if ((a instanceof URL) === (b instanceof URL))
|
|
25
|
+
throw new Error(`one parameter should be local string key, and the other a remote URL object`)
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
// because other files are the result of encode_filename,
|
|
32
|
-
// which always ends with a ".XX" (for handling insensitive filesystems)
|
|
33
|
-
temp_folder = `${braid_blob.meta_folder}/temp`
|
|
34
|
-
await require('fs').promises.mkdir(temp_folder, { recursive: true })
|
|
27
|
+
// One is local, one is remote - make a=local and b=remote (swap if not)
|
|
28
|
+
if (a instanceof URL) {
|
|
29
|
+
let swap = a; a = b; b = swap
|
|
30
|
+
}
|
|
35
31
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
32
|
+
var ac = null
|
|
33
|
+
options.signal?.addEventListener('abort', () => ac?.abort())
|
|
34
|
+
|
|
35
|
+
function handle_error(e) {
|
|
36
|
+
if (options.signal?.aborted) return
|
|
37
|
+
console.log(`disconnected from ${b.href}, retrying in ${braid_blob.reconnect_delay_ms ?? 1000}ms`)
|
|
38
|
+
setTimeout(connect, braid_blob.reconnect_delay_ms ?? 1000)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function connect() {
|
|
42
|
+
if (options.signal?.aborted) return
|
|
43
|
+
if (options.on_pre_connect) await options.on_pre_connect()
|
|
44
|
+
|
|
45
|
+
// Abort stuff in the previous connect
|
|
46
|
+
ac?.abort()
|
|
47
|
+
ac = new AbortController()
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
// Check if remote has our current version (simple fork-point check)
|
|
51
|
+
var server_has_our_version = false
|
|
52
|
+
var local_version = (await braid_blob.get(a, {
|
|
53
|
+
...options,
|
|
54
|
+
signal: ac.signal,
|
|
55
|
+
head: true
|
|
56
|
+
}))?.version
|
|
57
|
+
if (local_version) {
|
|
58
|
+
var r = await braid_blob.get(b, {
|
|
59
|
+
...options,
|
|
60
|
+
signal: ac.signal,
|
|
61
|
+
head: true,
|
|
62
|
+
dont_retry: true,
|
|
63
|
+
version: local_version,
|
|
64
|
+
})
|
|
65
|
+
server_has_our_version = !!r
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Local -> remote
|
|
69
|
+
await braid_blob.get(a, {
|
|
70
|
+
...options,
|
|
71
|
+
signal: ac.signal,
|
|
72
|
+
parents: server_has_our_version ? local_version : null,
|
|
73
|
+
subscribe: async update => {
|
|
55
74
|
try {
|
|
56
|
-
|
|
75
|
+
if (update.delete) {
|
|
76
|
+
var x = await braid_blob.delete(b, {
|
|
77
|
+
...options,
|
|
78
|
+
signal: ac.signal,
|
|
79
|
+
dont_retry: true,
|
|
80
|
+
content_type: update.content_type,
|
|
81
|
+
})
|
|
82
|
+
if (!x.ok) handle_error(new Error('failed to delete'))
|
|
83
|
+
} else {
|
|
84
|
+
var x = await braid_blob.put(b, update.body, {
|
|
85
|
+
...options,
|
|
86
|
+
signal: ac.signal,
|
|
87
|
+
dont_retry: true,
|
|
88
|
+
version: update.version,
|
|
89
|
+
content_type: update.content_type,
|
|
90
|
+
})
|
|
91
|
+
if ((x.status === 401 || x.status === 403) && options.on_unauthorized) {
|
|
92
|
+
await options.on_unauthorized?.()
|
|
93
|
+
} else if (!x.ok) handle_error(new Error('failed to PUT: ' + x.status))
|
|
94
|
+
}
|
|
57
95
|
} catch (e) {
|
|
58
|
-
if (e.
|
|
96
|
+
if (e.name !== 'AbortError')
|
|
97
|
+
handle_error(e)
|
|
59
98
|
}
|
|
60
99
|
}
|
|
61
|
-
}
|
|
62
|
-
} else {
|
|
63
|
-
// db_folder is already an object with read/write/delete
|
|
64
|
-
braid_blob.db = braid_blob.db_folder
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// establish a peer id if not already set
|
|
68
|
-
if (!braid_blob.peer)
|
|
69
|
-
braid_blob.peer = Math.random().toString(36).slice(2)
|
|
70
|
-
}
|
|
71
|
-
}
|
|
100
|
+
})
|
|
72
101
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
102
|
+
// Remote -> local
|
|
103
|
+
var remote_res = await braid_blob.get(b, {
|
|
104
|
+
...options,
|
|
105
|
+
signal: ac.signal,
|
|
106
|
+
dont_retry: true,
|
|
107
|
+
parents: local_version,
|
|
108
|
+
subscribe: async update => {
|
|
109
|
+
if (update.delete) await braid_blob.delete(a, {
|
|
110
|
+
...options,
|
|
111
|
+
signal: ac.signal,
|
|
112
|
+
content_type: update.content_type,
|
|
113
|
+
})
|
|
114
|
+
else await braid_blob.put(a, update.body, {
|
|
115
|
+
...options,
|
|
116
|
+
signal: ac.signal,
|
|
117
|
+
version: update.version,
|
|
118
|
+
content_type: update.content_type,
|
|
119
|
+
})
|
|
120
|
+
},
|
|
121
|
+
on_error: e => {
|
|
122
|
+
options.on_disconnect?.()
|
|
123
|
+
handle_error(e)
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
options.on_res?.(remote_res)
|
|
79
127
|
} catch (e) {
|
|
80
|
-
|
|
81
|
-
braid_blob.meta_cache[key] = {}
|
|
82
|
-
else throw e
|
|
128
|
+
handle_error(e)
|
|
83
129
|
}
|
|
84
130
|
}
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
async function save_meta(key) {
|
|
89
|
-
await atomic_write(`${braid_blob.meta_folder}/${encode_filename(key)}`,
|
|
90
|
-
JSON.stringify(braid_blob.meta_cache[key]), temp_folder)
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
async function delete_meta(key) {
|
|
94
|
-
delete braid_blob.meta_cache[key]
|
|
95
|
-
try {
|
|
96
|
-
await require('fs').promises.unlink(
|
|
97
|
-
`${braid_blob.meta_folder}/${encode_filename(key)}`)
|
|
98
|
-
} catch (e) {
|
|
99
|
-
if (e.code !== 'ENOENT') throw e
|
|
100
|
-
}
|
|
131
|
+
connect()
|
|
101
132
|
}
|
|
102
133
|
|
|
103
|
-
braid_blob.
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
// Handle URL case - make a remote PUT request
|
|
107
|
-
if (key instanceof URL) {
|
|
108
|
-
var params = {
|
|
109
|
-
method: 'PUT',
|
|
110
|
-
signal: options.signal,
|
|
111
|
-
body
|
|
112
|
-
}
|
|
113
|
-
if (!options.dont_retry)
|
|
114
|
-
params.retry = () => true
|
|
115
|
-
for (var x of ['headers', 'version', 'peer'])
|
|
116
|
-
if (options[x] != null) params[x] = options[x]
|
|
117
|
-
if (options.content_type)
|
|
118
|
-
params.headers = { ...params.headers,
|
|
119
|
-
'Content-Type': options.content_type }
|
|
134
|
+
braid_blob.serve = async (req, res, options = {}) => {
|
|
135
|
+
await braid_blob.init()
|
|
120
136
|
|
|
121
|
-
|
|
137
|
+
if (!options.key) {
|
|
138
|
+
var url = new URL(req.url, 'http://localhost')
|
|
139
|
+
options.key = url.pathname
|
|
122
140
|
}
|
|
123
141
|
|
|
124
|
-
|
|
125
|
-
if (
|
|
126
|
-
|
|
127
|
-
return await within_fiber(key, async () => {
|
|
128
|
-
var meta = await get_meta(key)
|
|
129
|
-
if (options.signal?.aborted) return
|
|
142
|
+
braidify(req, res)
|
|
143
|
+
if (res.is_multiplexer) return
|
|
130
144
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
`${braid_blob.peer}-${max_seq('' + Date.now(),
|
|
134
|
-
meta.event ? increment_seq(get_event_seq(meta.event)) : '')}`
|
|
145
|
+
// Handle OPTIONS request
|
|
146
|
+
if (req.method === 'OPTIONS') return res.end()
|
|
135
147
|
|
|
136
|
-
|
|
137
|
-
|
|
148
|
+
// consume PUT body
|
|
149
|
+
var body = req.method === 'PUT' && await slurp(req)
|
|
138
150
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
151
|
+
if (req.method === 'GET' || req.method === 'HEAD') {
|
|
152
|
+
if (!res.hasHeader("editable")) res.setHeader("Editable", "true")
|
|
153
|
+
if (!req.subscribe) res.setHeader("Accept-Subscribe", "true")
|
|
154
|
+
res.setHeader("Merge-Type", "aww")
|
|
142
155
|
|
|
143
|
-
|
|
144
|
-
|
|
156
|
+
try {
|
|
157
|
+
var result = await braid_blob.get(options.key, {
|
|
158
|
+
peer: req.peer,
|
|
159
|
+
head: req.method === "HEAD",
|
|
160
|
+
version: req.version,
|
|
161
|
+
parents: req.parents,
|
|
162
|
+
header_cb: (result) => {
|
|
163
|
+
res.setHeader((req.subscribe ? "Current-" : "") +
|
|
164
|
+
"Version", version_to_header(result.version))
|
|
165
|
+
if (result.content_type)
|
|
166
|
+
res.setHeader('Content-Type', result.content_type)
|
|
167
|
+
},
|
|
168
|
+
before_send_cb: () => res.startSubscription(),
|
|
169
|
+
subscribe: req.subscribe ? (update) => {
|
|
170
|
+
if (update.delete) {
|
|
171
|
+
update.status = 404
|
|
172
|
+
delete update.delete
|
|
173
|
+
}
|
|
174
|
+
if (update.content_type) {
|
|
175
|
+
update['Content-Type'] = update.content_type
|
|
176
|
+
delete update.content_type
|
|
177
|
+
}
|
|
178
|
+
update['Merge-Type'] = 'aww'
|
|
179
|
+
res.sendUpdate(update)
|
|
180
|
+
} : null
|
|
181
|
+
})
|
|
182
|
+
} catch (e) {
|
|
183
|
+
if (e.message && e.message.startsWith('unknown version')) {
|
|
184
|
+
// Server doesn't have this version
|
|
185
|
+
res.statusCode = 309
|
|
186
|
+
res.statusMessage = 'Version Unknown Here'
|
|
187
|
+
return res.end('')
|
|
188
|
+
} else throw e
|
|
189
|
+
}
|
|
145
190
|
|
|
146
|
-
|
|
147
|
-
|
|
191
|
+
if (!result) {
|
|
192
|
+
res.statusCode = 404
|
|
193
|
+
return res.end('File Not Found')
|
|
194
|
+
}
|
|
148
195
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
content_type: meta.content_type,
|
|
154
|
-
body
|
|
155
|
-
}
|
|
156
|
-
if (braid_blob.key_to_subs[key])
|
|
157
|
-
for (var [peer, sub] of braid_blob.key_to_subs[key].entries())
|
|
158
|
-
if (!options.peer || options.peer !== peer)
|
|
159
|
-
await sub.sendUpdate(update)
|
|
196
|
+
if (result.content_type && req.headers.accept &&
|
|
197
|
+
!isAcceptable(result.content_type, req.headers.accept)) {
|
|
198
|
+
res.statusCode = 406
|
|
199
|
+
return res.end(`Content-Type of ${result.content_type} not in Accept: ${req.headers.accept}`)
|
|
160
200
|
}
|
|
161
201
|
|
|
162
|
-
return
|
|
163
|
-
|
|
202
|
+
if (req.method == "HEAD") return res.end('')
|
|
203
|
+
else if (!req.subscribe) return res.end(result.body)
|
|
204
|
+
else {
|
|
205
|
+
// If no immediate update was sent,
|
|
206
|
+
// get the node http code to send headers
|
|
207
|
+
if (!result.sent) res.write('\n\n')
|
|
208
|
+
}
|
|
209
|
+
} else if (req.method === 'PUT') {
|
|
210
|
+
// Handle PUT request to update binary files
|
|
211
|
+
var event = await braid_blob.put(options.key, body, {
|
|
212
|
+
version: req.version,
|
|
213
|
+
content_type: req.headers['content-type'],
|
|
214
|
+
peer: req.peer
|
|
215
|
+
})
|
|
216
|
+
res.setHeader("Version", version_to_header(event != null ? [event] : []))
|
|
217
|
+
res.end('')
|
|
218
|
+
} else if (req.method === 'DELETE') {
|
|
219
|
+
await braid_blob.delete(options.key, {
|
|
220
|
+
content_type: req.headers['content-type'],
|
|
221
|
+
peer: req.peer
|
|
222
|
+
})
|
|
223
|
+
res.end('')
|
|
224
|
+
}
|
|
164
225
|
}
|
|
165
226
|
|
|
166
227
|
braid_blob.get = async (key, options = {}) => {
|
|
@@ -277,6 +338,69 @@ function create_braid_blob() {
|
|
|
277
338
|
})
|
|
278
339
|
}
|
|
279
340
|
|
|
341
|
+
braid_blob.put = async (key, body, options = {}) => {
|
|
342
|
+
options = normalize_options(options)
|
|
343
|
+
|
|
344
|
+
// Handle URL case - make a remote PUT request
|
|
345
|
+
if (key instanceof URL) {
|
|
346
|
+
var params = {
|
|
347
|
+
method: 'PUT',
|
|
348
|
+
signal: options.signal,
|
|
349
|
+
body
|
|
350
|
+
}
|
|
351
|
+
if (!options.dont_retry)
|
|
352
|
+
params.retry = () => true
|
|
353
|
+
for (var x of ['headers', 'version', 'peer'])
|
|
354
|
+
if (options[x] != null) params[x] = options[x]
|
|
355
|
+
if (options.content_type)
|
|
356
|
+
params.headers = { ...params.headers,
|
|
357
|
+
'Content-Type': options.content_type }
|
|
358
|
+
|
|
359
|
+
return await braid_fetch(key.href, params)
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
await braid_blob.init()
|
|
363
|
+
if (options.signal?.aborted) return
|
|
364
|
+
|
|
365
|
+
return await within_fiber(key, async () => {
|
|
366
|
+
var meta = await get_meta(key)
|
|
367
|
+
if (options.signal?.aborted) return
|
|
368
|
+
|
|
369
|
+
var their_e = options.version ? options.version[0] :
|
|
370
|
+
// we'll give them a event id in this case
|
|
371
|
+
`${braid_blob.peer}-${max_seq('' + Date.now(),
|
|
372
|
+
meta.event ? increment_seq(get_event_seq(meta.event)) : '')}`
|
|
373
|
+
|
|
374
|
+
if (compare_events(their_e, meta.event) > 0) {
|
|
375
|
+
meta.event = their_e
|
|
376
|
+
|
|
377
|
+
if (!options.skip_write)
|
|
378
|
+
await (options.db || braid_blob.db).write(key, body)
|
|
379
|
+
if (options.signal?.aborted) return
|
|
380
|
+
|
|
381
|
+
if (options.content_type)
|
|
382
|
+
meta.content_type = options.content_type
|
|
383
|
+
|
|
384
|
+
await save_meta(key)
|
|
385
|
+
if (options.signal?.aborted) return
|
|
386
|
+
|
|
387
|
+
// Notify all subscriptions of the update
|
|
388
|
+
// (except the peer which made the PUT request itself)
|
|
389
|
+
var update = {
|
|
390
|
+
version: [meta.event],
|
|
391
|
+
content_type: meta.content_type,
|
|
392
|
+
body
|
|
393
|
+
}
|
|
394
|
+
if (braid_blob.key_to_subs[key])
|
|
395
|
+
for (var [peer, sub] of braid_blob.key_to_subs[key].entries())
|
|
396
|
+
if (!options.peer || options.peer !== peer)
|
|
397
|
+
await sub.sendUpdate(update)
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return meta.event
|
|
401
|
+
})
|
|
402
|
+
}
|
|
403
|
+
|
|
280
404
|
braid_blob.delete = async (key, options = {}) => {
|
|
281
405
|
options = normalize_options(options)
|
|
282
406
|
|
|
@@ -321,241 +445,96 @@ function create_braid_blob() {
|
|
|
321
445
|
})
|
|
322
446
|
}
|
|
323
447
|
|
|
324
|
-
braid_blob.
|
|
448
|
+
braid_blob.init = async () => {
|
|
449
|
+
// We only want to initialize once
|
|
450
|
+
var init_p = real_init()
|
|
451
|
+
braid_blob.init = () => init_p
|
|
325
452
|
await braid_blob.init()
|
|
326
453
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
braidify(req, res)
|
|
333
|
-
if (res.is_multiplexer) return
|
|
334
|
-
|
|
335
|
-
// Handle OPTIONS request
|
|
336
|
-
if (req.method === 'OPTIONS') return res.end()
|
|
337
|
-
|
|
338
|
-
// consume PUT body
|
|
339
|
-
var body = req.method === 'PUT' && await slurp(req)
|
|
454
|
+
async function real_init() {
|
|
455
|
+
// Ensure our meta folder exists
|
|
456
|
+
await require('fs').promises.mkdir(braid_blob.meta_folder, { recursive: true })
|
|
340
457
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
458
|
+
// Create a temp folder inside the meta folder for writing temp files,
|
|
459
|
+
// for atomic writing.
|
|
460
|
+
// The temp folder is called "temp",
|
|
461
|
+
// And this is guaranteed not to conflict with any other files,
|
|
462
|
+
// because other files are the result of encode_filename,
|
|
463
|
+
// which always ends with a ".XX" (for handling insensitive filesystems)
|
|
464
|
+
temp_folder = `${braid_blob.meta_folder}/temp`
|
|
465
|
+
await require('fs').promises.mkdir(temp_folder, { recursive: true })
|
|
345
466
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
},
|
|
358
|
-
before_send_cb: () => res.startSubscription(),
|
|
359
|
-
subscribe: req.subscribe ? (update) => {
|
|
360
|
-
if (update.delete) {
|
|
361
|
-
update.status = 404
|
|
362
|
-
delete update.delete
|
|
467
|
+
// Set up db - either use provided object or create file-based storage
|
|
468
|
+
if (typeof braid_blob.db_folder === 'string') {
|
|
469
|
+
await require('fs').promises.mkdir(braid_blob.db_folder, { recursive: true })
|
|
470
|
+
braid_blob.db = {
|
|
471
|
+
read: async (key) => {
|
|
472
|
+
var file_path = `${braid_blob.db_folder}/${encode_filename(key)}`
|
|
473
|
+
try {
|
|
474
|
+
return await require('fs').promises.readFile(file_path)
|
|
475
|
+
} catch (e) {
|
|
476
|
+
if (e.code === 'ENOENT') return null
|
|
477
|
+
throw e
|
|
363
478
|
}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
479
|
+
},
|
|
480
|
+
write: async (key, data) => {
|
|
481
|
+
var file_path = `${braid_blob.db_folder}/${encode_filename(key)}`
|
|
482
|
+
await atomic_write(file_path, data, temp_folder)
|
|
483
|
+
},
|
|
484
|
+
delete: async (key) => {
|
|
485
|
+
var file_path = `${braid_blob.db_folder}/${encode_filename(key)}`
|
|
486
|
+
try {
|
|
487
|
+
await require('fs').promises.unlink(file_path)
|
|
488
|
+
} catch (e) {
|
|
489
|
+
if (e.code !== 'ENOENT') throw e
|
|
367
490
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
if (e.message && e.message.startsWith('unknown version')) {
|
|
374
|
-
// Server doesn't have this version
|
|
375
|
-
res.statusCode = 309
|
|
376
|
-
res.statusMessage = 'Version Unknown Here'
|
|
377
|
-
return res.end('')
|
|
378
|
-
} else throw e
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
if (!result) {
|
|
382
|
-
res.statusCode = 404
|
|
383
|
-
return res.end('File Not Found')
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
if (result.content_type && req.headers.accept &&
|
|
387
|
-
!isAcceptable(result.content_type, req.headers.accept)) {
|
|
388
|
-
res.statusCode = 406
|
|
389
|
-
return res.end(`Content-Type of ${result.content_type} not in Accept: ${req.headers.accept}`)
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
} else {
|
|
494
|
+
// db_folder is already an object with read/write/delete
|
|
495
|
+
braid_blob.db = braid_blob.db_folder
|
|
390
496
|
}
|
|
391
497
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
// If no immediate update was sent,
|
|
396
|
-
// get the node http code to send headers
|
|
397
|
-
if (!result.sent) res.write('\n\n')
|
|
398
|
-
}
|
|
399
|
-
} else if (req.method === 'PUT') {
|
|
400
|
-
// Handle PUT request to update binary files
|
|
401
|
-
var event = await braid_blob.put(options.key, body, {
|
|
402
|
-
version: req.version,
|
|
403
|
-
content_type: req.headers['content-type'],
|
|
404
|
-
peer: req.peer
|
|
405
|
-
})
|
|
406
|
-
res.setHeader("Version", version_to_header(event != null ? [event] : []))
|
|
407
|
-
res.end('')
|
|
408
|
-
} else if (req.method === 'DELETE') {
|
|
409
|
-
await braid_blob.delete(options.key, {
|
|
410
|
-
content_type: req.headers['content-type'],
|
|
411
|
-
peer: req.peer
|
|
412
|
-
})
|
|
413
|
-
res.end('')
|
|
498
|
+
// establish a peer id if not already set
|
|
499
|
+
if (!braid_blob.peer)
|
|
500
|
+
braid_blob.peer = Math.random().toString(36).slice(2)
|
|
414
501
|
}
|
|
415
502
|
}
|
|
416
503
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
content_type: update.content_type,
|
|
428
|
-
})
|
|
429
|
-
else await braid_blob.put(b, update.body, {
|
|
430
|
-
...options,
|
|
431
|
-
version: update.version,
|
|
432
|
-
content_type: update.content_type,
|
|
433
|
-
})
|
|
434
|
-
}
|
|
435
|
-
})
|
|
436
|
-
braid_blob.get(b, {
|
|
437
|
-
...options,
|
|
438
|
-
subscribe: async update => {
|
|
439
|
-
if (update.delete) await braid_blob.delete(a, {
|
|
440
|
-
...options,
|
|
441
|
-
content_type: update.content_type,
|
|
442
|
-
})
|
|
443
|
-
else await braid_blob.put(a, update.body, {
|
|
444
|
-
...options,
|
|
445
|
-
version: update.version,
|
|
446
|
-
content_type: update.content_type,
|
|
447
|
-
})
|
|
448
|
-
}
|
|
449
|
-
})
|
|
450
|
-
} else {
|
|
451
|
-
// One is local, one is remote - make a=local and b=remote (swap if not)
|
|
452
|
-
if (a instanceof URL) {
|
|
453
|
-
let swap = a; a = b; b = swap
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
var ac = null
|
|
457
|
-
options.signal?.addEventListener('abort', () => ac?.abort())
|
|
458
|
-
|
|
459
|
-
function handle_error(e) {
|
|
460
|
-
if (options.signal?.aborted) return
|
|
461
|
-
console.log(`disconnected from ${b.href}, retrying in ${braid_blob.reconnect_delay_ms ?? 1000}ms`)
|
|
462
|
-
setTimeout(connect, braid_blob.reconnect_delay_ms ?? 1000)
|
|
504
|
+
async function get_meta(key) {
|
|
505
|
+
if (!braid_blob.meta_cache[key]) {
|
|
506
|
+
try {
|
|
507
|
+
braid_blob.meta_cache[key] = JSON.parse(
|
|
508
|
+
await require('fs').promises.readFile(
|
|
509
|
+
`${braid_blob.meta_folder}/${encode_filename(key)}`, 'utf8'))
|
|
510
|
+
} catch (e) {
|
|
511
|
+
if (e.code === 'ENOENT')
|
|
512
|
+
braid_blob.meta_cache[key] = {}
|
|
513
|
+
else throw e
|
|
463
514
|
}
|
|
515
|
+
}
|
|
516
|
+
return braid_blob.meta_cache[key]
|
|
517
|
+
}
|
|
464
518
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
// Abort stuff in the previous connect
|
|
470
|
-
ac?.abort()
|
|
471
|
-
ac = new AbortController()
|
|
472
|
-
|
|
473
|
-
try {
|
|
474
|
-
// Check if remote has our current version (simple fork-point check)
|
|
475
|
-
var server_has_our_version = false
|
|
476
|
-
var local_version = (await braid_blob.get(a, {
|
|
477
|
-
...options,
|
|
478
|
-
signal: ac.signal,
|
|
479
|
-
head: true
|
|
480
|
-
}))?.version
|
|
481
|
-
if (local_version) {
|
|
482
|
-
var r = await braid_blob.get(b, {
|
|
483
|
-
...options,
|
|
484
|
-
signal: ac.signal,
|
|
485
|
-
head: true,
|
|
486
|
-
dont_retry: true,
|
|
487
|
-
version: local_version,
|
|
488
|
-
})
|
|
489
|
-
server_has_our_version = !!r
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
// Local -> remote
|
|
493
|
-
await braid_blob.get(a, {
|
|
494
|
-
...options,
|
|
495
|
-
signal: ac.signal,
|
|
496
|
-
parents: server_has_our_version ? local_version : null,
|
|
497
|
-
subscribe: async update => {
|
|
498
|
-
try {
|
|
499
|
-
if (update.delete) {
|
|
500
|
-
var x = await braid_blob.delete(b, {
|
|
501
|
-
...options,
|
|
502
|
-
signal: ac.signal,
|
|
503
|
-
dont_retry: true,
|
|
504
|
-
content_type: update.content_type,
|
|
505
|
-
})
|
|
506
|
-
if (!x.ok) handle_error(new Error('failed to delete'))
|
|
507
|
-
} else {
|
|
508
|
-
var x = await braid_blob.put(b, update.body, {
|
|
509
|
-
...options,
|
|
510
|
-
signal: ac.signal,
|
|
511
|
-
dont_retry: true,
|
|
512
|
-
version: update.version,
|
|
513
|
-
content_type: update.content_type,
|
|
514
|
-
})
|
|
515
|
-
if ((x.status === 401 || x.status === 403) && options.on_unauthorized) {
|
|
516
|
-
await options.on_unauthorized?.()
|
|
517
|
-
} else if (!x.ok) handle_error(new Error('failed to PUT: ' + x.status))
|
|
518
|
-
}
|
|
519
|
-
} catch (e) {
|
|
520
|
-
if (e.name !== 'AbortError')
|
|
521
|
-
handle_error(e)
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
})
|
|
519
|
+
async function save_meta(key) {
|
|
520
|
+
await atomic_write(`${braid_blob.meta_folder}/${encode_filename(key)}`,
|
|
521
|
+
JSON.stringify(braid_blob.meta_cache[key]), temp_folder)
|
|
522
|
+
}
|
|
525
523
|
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
if (update.delete) await braid_blob.delete(a, {
|
|
534
|
-
...options,
|
|
535
|
-
signal: ac.signal,
|
|
536
|
-
content_type: update.content_type,
|
|
537
|
-
})
|
|
538
|
-
else await braid_blob.put(a, update.body, {
|
|
539
|
-
...options,
|
|
540
|
-
signal: ac.signal,
|
|
541
|
-
version: update.version,
|
|
542
|
-
content_type: update.content_type,
|
|
543
|
-
})
|
|
544
|
-
},
|
|
545
|
-
on_error: e => {
|
|
546
|
-
options.on_disconnect?.()
|
|
547
|
-
handle_error(e)
|
|
548
|
-
}
|
|
549
|
-
})
|
|
550
|
-
options.on_res?.(remote_res)
|
|
551
|
-
} catch (e) {
|
|
552
|
-
handle_error(e)
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
connect()
|
|
524
|
+
async function delete_meta(key) {
|
|
525
|
+
delete braid_blob.meta_cache[key]
|
|
526
|
+
try {
|
|
527
|
+
await require('fs').promises.unlink(
|
|
528
|
+
`${braid_blob.meta_folder}/${encode_filename(key)}`)
|
|
529
|
+
} catch (e) {
|
|
530
|
+
if (e.code !== 'ENOENT') throw e
|
|
556
531
|
}
|
|
557
532
|
}
|
|
558
533
|
|
|
534
|
+
//////////////////////////////////////////////////////////////////
|
|
535
|
+
//////////////////////////////////////////////////////////////////
|
|
536
|
+
//////////////////////////////////////////////////////////////////
|
|
537
|
+
|
|
559
538
|
function compare_events(a, b) {
|
|
560
539
|
if (!a) a = ''
|
|
561
540
|
if (!b) b = ''
|
|
@@ -629,7 +608,7 @@ function create_braid_blob() {
|
|
|
629
608
|
})
|
|
630
609
|
return within_fiber.chains[id] = curr
|
|
631
610
|
}
|
|
632
|
-
|
|
611
|
+
|
|
633
612
|
async function slurp(req) {
|
|
634
613
|
return await new Promise(done => {
|
|
635
614
|
var chunks = []
|
|
@@ -695,19 +674,6 @@ function create_braid_blob() {
|
|
|
695
674
|
}
|
|
696
675
|
}
|
|
697
676
|
|
|
698
|
-
function get_header(headers, key) {
|
|
699
|
-
if (!headers) return
|
|
700
|
-
|
|
701
|
-
// optimization..
|
|
702
|
-
if (headers.hasOwnProperty(key))
|
|
703
|
-
return headers[key]
|
|
704
|
-
|
|
705
|
-
var lowerKey = key.toLowerCase()
|
|
706
|
-
for (var headerKey of Object.keys(headers))
|
|
707
|
-
if (headerKey.toLowerCase() === lowerKey)
|
|
708
|
-
return headers[headerKey]
|
|
709
|
-
}
|
|
710
|
-
|
|
711
677
|
function normalize_options(options = {}) {
|
|
712
678
|
if (!normalize_options.special) {
|
|
713
679
|
normalize_options.special = {
|
package/package.json
CHANGED
package/test/tests.js
CHANGED
|
@@ -996,7 +996,7 @@ runTest(
|
|
|
996
996
|
)
|
|
997
997
|
|
|
998
998
|
runTest(
|
|
999
|
-
"test sync two local keys",
|
|
999
|
+
"test sync two local keys throws error",
|
|
1000
1000
|
async () => {
|
|
1001
1001
|
var key1 = '/test-sync-local1-' + Math.random().toString(36).slice(2)
|
|
1002
1002
|
var key2 = '/test-sync-local2-' + Math.random().toString(36).slice(2)
|
|
@@ -1007,29 +1007,18 @@ runTest(
|
|
|
1007
1007
|
try {
|
|
1008
1008
|
var braid_blob = require(\`\${__dirname}/../index.js\`)
|
|
1009
1009
|
|
|
1010
|
-
//
|
|
1011
|
-
await braid_blob.put('${key1}', Buffer.from('sync local content'), { version: ['700'] })
|
|
1012
|
-
|
|
1013
|
-
// Start sync between two local keys
|
|
1010
|
+
// Try to sync between two local keys - should throw
|
|
1014
1011
|
braid_blob.sync('${key1}', '${key2}')
|
|
1015
1012
|
|
|
1016
|
-
res.end('
|
|
1013
|
+
res.end('no error thrown')
|
|
1017
1014
|
} catch (e) {
|
|
1018
|
-
res.end('error
|
|
1015
|
+
res.end('error thrown')
|
|
1019
1016
|
}
|
|
1020
1017
|
})()`
|
|
1021
1018
|
})
|
|
1022
|
-
|
|
1023
|
-
if (result.startsWith('error:')) return result
|
|
1024
|
-
|
|
1025
|
-
// Wait a bit for sync to happen
|
|
1026
|
-
await new Promise(done => setTimeout(done, 100))
|
|
1027
|
-
|
|
1028
|
-
// Check second key has the content
|
|
1029
|
-
var r = await braid_fetch(`${key2}`)
|
|
1030
|
-
return await r.text()
|
|
1019
|
+
return await r1.text()
|
|
1031
1020
|
},
|
|
1032
|
-
'
|
|
1021
|
+
'error thrown'
|
|
1033
1022
|
)
|
|
1034
1023
|
|
|
1035
1024
|
runTest(
|