braid-blob 0.0.39 → 0.0.41
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/AI-README.md +13 -32
- package/index.js +325 -320
- package/package.json +1 -1
- package/test/tests.js +199 -78
package/index.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
var {http_server: braidify, fetch: braid_fetch} = require('braid-http')
|
|
2
|
-
fs = require('fs'),
|
|
3
|
-
path = require('path')
|
|
1
|
+
var {http_server: braidify, fetch: braid_fetch} = require('braid-http')
|
|
4
2
|
|
|
5
3
|
function create_braid_blob() {
|
|
6
4
|
var braid_blob = {
|
|
@@ -21,29 +19,29 @@ function create_braid_blob() {
|
|
|
21
19
|
|
|
22
20
|
async function real_init() {
|
|
23
21
|
// Ensure our meta folder exists
|
|
24
|
-
await fs.promises.mkdir(braid_blob.meta_folder, { recursive: true })
|
|
22
|
+
await require('fs').promises.mkdir(braid_blob.meta_folder, { recursive: true })
|
|
25
23
|
|
|
26
24
|
// Set up db - either use provided object or create file-based storage
|
|
27
25
|
if (typeof braid_blob.db_folder === 'string') {
|
|
28
|
-
await fs.promises.mkdir(braid_blob.db_folder, { recursive: true })
|
|
26
|
+
await require('fs').promises.mkdir(braid_blob.db_folder, { recursive: true })
|
|
29
27
|
braid_blob.db = {
|
|
30
28
|
read: async (key) => {
|
|
31
|
-
var file_path =
|
|
29
|
+
var file_path = `${braid_blob.db_folder}/${encode_filename(key)}`
|
|
32
30
|
try {
|
|
33
|
-
return await fs.promises.readFile(file_path)
|
|
31
|
+
return await require('fs').promises.readFile(file_path)
|
|
34
32
|
} catch (e) {
|
|
35
33
|
if (e.code === 'ENOENT') return null
|
|
36
34
|
throw e
|
|
37
35
|
}
|
|
38
36
|
},
|
|
39
37
|
write: async (key, data) => {
|
|
40
|
-
var file_path =
|
|
41
|
-
await fs.promises.writeFile(file_path, data)
|
|
38
|
+
var file_path = `${braid_blob.db_folder}/${encode_filename(key)}`
|
|
39
|
+
await require('fs').promises.writeFile(file_path, data)
|
|
42
40
|
},
|
|
43
41
|
delete: async (key) => {
|
|
44
|
-
var file_path =
|
|
42
|
+
var file_path = `${braid_blob.db_folder}/${encode_filename(key)}`
|
|
45
43
|
try {
|
|
46
|
-
await fs.promises.unlink(file_path)
|
|
44
|
+
await require('fs').promises.unlink(file_path)
|
|
47
45
|
} catch (e) {
|
|
48
46
|
if (e.code !== 'ENOENT') throw e
|
|
49
47
|
}
|
|
@@ -60,32 +58,32 @@ function create_braid_blob() {
|
|
|
60
58
|
}
|
|
61
59
|
}
|
|
62
60
|
|
|
63
|
-
function get_meta(key) {
|
|
64
|
-
if (braid_blob.meta_cache[key])
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
61
|
+
async function get_meta(key) {
|
|
62
|
+
if (!braid_blob.meta_cache[key]) {
|
|
63
|
+
try {
|
|
64
|
+
braid_blob.meta_cache[key] = JSON.parse(
|
|
65
|
+
await require('fs').promises.readFile(
|
|
66
|
+
`${braid_blob.meta_folder}/${encode_filename(key)}`, 'utf8'))
|
|
67
|
+
} catch (e) {
|
|
68
|
+
if (e.code === 'ENOENT')
|
|
69
|
+
braid_blob.meta_cache[key] = {}
|
|
70
|
+
else throw e
|
|
71
|
+
}
|
|
73
72
|
}
|
|
73
|
+
return braid_blob.meta_cache[key]
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
async function
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
var meta_path = path.join(braid_blob.meta_folder, encode_filename(key))
|
|
81
|
-
await fs.promises.writeFile(meta_path, JSON.stringify(meta))
|
|
76
|
+
async function save_meta(key) {
|
|
77
|
+
await require('fs').promises.writeFile(
|
|
78
|
+
`${braid_blob.meta_folder}/${encode_filename(key)}`,
|
|
79
|
+
JSON.stringify(braid_blob.meta_cache[key]))
|
|
82
80
|
}
|
|
83
81
|
|
|
84
82
|
async function delete_meta(key) {
|
|
85
83
|
delete braid_blob.meta_cache[key]
|
|
86
|
-
var meta_path = path.join(braid_blob.meta_folder, encode_filename(key))
|
|
87
84
|
try {
|
|
88
|
-
await fs.promises.unlink(
|
|
85
|
+
await require('fs').promises.unlink(
|
|
86
|
+
`${braid_blob.meta_folder}/${encode_filename(key)}`)
|
|
89
87
|
} catch (e) {
|
|
90
88
|
if (e.code !== 'ENOENT') throw e
|
|
91
89
|
}
|
|
@@ -96,11 +94,10 @@ function create_braid_blob() {
|
|
|
96
94
|
|
|
97
95
|
// Handle URL case - make a remote PUT request
|
|
98
96
|
if (key instanceof URL) {
|
|
99
|
-
|
|
100
97
|
var params = {
|
|
101
98
|
method: 'PUT',
|
|
102
99
|
signal: options.signal,
|
|
103
|
-
body
|
|
100
|
+
body
|
|
104
101
|
}
|
|
105
102
|
if (!options.dont_retry)
|
|
106
103
|
params.retry = () => true
|
|
@@ -116,48 +113,43 @@ function create_braid_blob() {
|
|
|
116
113
|
await braid_blob.init()
|
|
117
114
|
if (options.signal?.aborted) return
|
|
118
115
|
|
|
119
|
-
|
|
116
|
+
return await within_fiber(key, async () => {
|
|
117
|
+
var meta = await get_meta(key)
|
|
118
|
+
if (options.signal?.aborted) return
|
|
120
119
|
|
|
121
|
-
|
|
122
|
-
!options.version ?
|
|
120
|
+
var their_e = options.version ? options.version[0] :
|
|
123
121
|
// we'll give them a event id in this case
|
|
124
|
-
`${braid_blob.peer}-${
|
|
125
|
-
meta.event ?
|
|
126
|
-
!options.version.length ?
|
|
127
|
-
null :
|
|
128
|
-
options.version[0]
|
|
129
|
-
|
|
130
|
-
if (their_e != null &&
|
|
131
|
-
(meta.event == null ||
|
|
132
|
-
compare_events(their_e, meta.event) > 0)) {
|
|
133
|
-
meta.event = their_e
|
|
134
|
-
|
|
135
|
-
// Write the file using url-file-db (unless skip_write is set)
|
|
136
|
-
if (!options.skip_write)
|
|
137
|
-
await braid_blob.db.write(key, body)
|
|
138
|
-
if (options.signal?.aborted) return
|
|
122
|
+
`${braid_blob.peer}-${max_seq('' + Date.now(),
|
|
123
|
+
meta.event ? increment_seq(get_event_seq(meta.event)) : '')}`
|
|
139
124
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
if (options.content_type)
|
|
143
|
-
meta_updates.content_type = options.content_type
|
|
125
|
+
if (compare_events(their_e, meta.event) > 0) {
|
|
126
|
+
meta.event = their_e
|
|
144
127
|
|
|
145
|
-
|
|
146
|
-
|
|
128
|
+
if (!options.skip_write)
|
|
129
|
+
await (options.db || braid_blob.db).write(key, body)
|
|
130
|
+
if (options.signal?.aborted) return
|
|
147
131
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
132
|
+
if (options.content_type)
|
|
133
|
+
meta.content_type = options.content_type
|
|
134
|
+
|
|
135
|
+
await save_meta(key)
|
|
136
|
+
if (options.signal?.aborted) return
|
|
137
|
+
|
|
138
|
+
// Notify all subscriptions of the update
|
|
139
|
+
// (except the peer which made the PUT request itself)
|
|
140
|
+
var update = {
|
|
141
|
+
version: [meta.event],
|
|
142
|
+
content_type: meta.content_type,
|
|
143
|
+
body
|
|
144
|
+
}
|
|
145
|
+
if (braid_blob.key_to_subs[key])
|
|
146
|
+
for (var [peer, sub] of braid_blob.key_to_subs[key].entries())
|
|
147
|
+
if (!options.peer || options.peer !== peer)
|
|
148
|
+
await sub.sendUpdate(update)
|
|
149
|
+
}
|
|
159
150
|
|
|
160
|
-
|
|
151
|
+
return meta.event
|
|
152
|
+
})
|
|
161
153
|
}
|
|
162
154
|
|
|
163
155
|
braid_blob.get = async (key, options = {}) => {
|
|
@@ -167,7 +159,7 @@ function create_braid_blob() {
|
|
|
167
159
|
if (key instanceof URL) {
|
|
168
160
|
var params = {
|
|
169
161
|
signal: options.signal,
|
|
170
|
-
subscribe:
|
|
162
|
+
subscribe: options.subscribe,
|
|
171
163
|
heartbeats: 120,
|
|
172
164
|
}
|
|
173
165
|
if (!options.dont_retry) {
|
|
@@ -183,7 +175,9 @@ function create_braid_blob() {
|
|
|
183
175
|
|
|
184
176
|
var res = await braid_fetch(key.href, params)
|
|
185
177
|
|
|
186
|
-
if (!res.ok)
|
|
178
|
+
if (!res.ok)
|
|
179
|
+
if (options.subscribe) throw new Error('failed to subscribe')
|
|
180
|
+
else return null
|
|
187
181
|
|
|
188
182
|
var result = {}
|
|
189
183
|
if (res.version) result.version = res.version
|
|
@@ -192,6 +186,8 @@ function create_braid_blob() {
|
|
|
192
186
|
|
|
193
187
|
if (options.subscribe) {
|
|
194
188
|
res.subscribe(async update => {
|
|
189
|
+
if (update.status === 404) update.delete = true
|
|
190
|
+
update.content_type = update.extra_headers['content-type']
|
|
195
191
|
await options.subscribe(update)
|
|
196
192
|
}, e => options.on_error?.(e))
|
|
197
193
|
return res
|
|
@@ -202,70 +198,72 @@ function create_braid_blob() {
|
|
|
202
198
|
}
|
|
203
199
|
|
|
204
200
|
await braid_blob.init()
|
|
201
|
+
if (options.signal?.aborted) return
|
|
205
202
|
|
|
206
|
-
|
|
207
|
-
|
|
203
|
+
return await within_fiber(key, async () => {
|
|
204
|
+
var meta = await get_meta(key)
|
|
205
|
+
if (options.signal?.aborted) return
|
|
208
206
|
|
|
209
|
-
|
|
210
|
-
version: [meta.event],
|
|
211
|
-
content_type: meta.content_type || options.content_type
|
|
212
|
-
}
|
|
213
|
-
if (options.header_cb) await options.header_cb(result)
|
|
214
|
-
if (options.signal?.aborted) return
|
|
215
|
-
// Check if requested version/parents is newer than what we have - if so, we don't have it
|
|
216
|
-
if (options.version && options.version.length && compare_events(options.version[0], meta.event) > 0)
|
|
217
|
-
throw new Error('unknown version: ' + options.version)
|
|
218
|
-
if (options.parents && options.parents.length && compare_events(options.parents[0], meta.event) > 0)
|
|
219
|
-
throw new Error('unknown version: ' + options.parents)
|
|
220
|
-
if (options.head) return result
|
|
221
|
-
|
|
222
|
-
if (options.subscribe) {
|
|
223
|
-
var subscribe_chain = Promise.resolve()
|
|
224
|
-
options.my_subscribe = (x) => subscribe_chain =
|
|
225
|
-
subscribe_chain.then(() =>
|
|
226
|
-
!options.signal?.aborted && options.subscribe(x))
|
|
227
|
-
|
|
228
|
-
// Start a subscription for future updates
|
|
229
|
-
if (!braid_blob.key_to_subs[key])
|
|
230
|
-
braid_blob.key_to_subs[key] = new Map()
|
|
231
|
-
|
|
232
|
-
var peer = options.peer || Math.random().toString(36).slice(2)
|
|
233
|
-
braid_blob.key_to_subs[key].set(peer, {
|
|
234
|
-
sendUpdate: (update) => {
|
|
235
|
-
options.my_subscribe({
|
|
236
|
-
body: update.body,
|
|
237
|
-
version: update.version,
|
|
238
|
-
content_type: meta.content_type || options.content_type
|
|
239
|
-
})
|
|
240
|
-
}
|
|
241
|
-
})
|
|
207
|
+
if (!meta.event && !options.subscribe) return null
|
|
242
208
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
})
|
|
209
|
+
var result = {
|
|
210
|
+
version: meta.event ? [meta.event] : [],
|
|
211
|
+
content_type: meta.content_type
|
|
212
|
+
}
|
|
248
213
|
|
|
249
|
-
if (options.
|
|
214
|
+
if (options.header_cb) await options.header_cb(result)
|
|
250
215
|
if (options.signal?.aborted) return
|
|
251
216
|
|
|
252
|
-
//
|
|
253
|
-
if (!options.
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
217
|
+
// Check if requested version/parents is newer than what we have - if so, we don't have it
|
|
218
|
+
if (!options.subscribe) {
|
|
219
|
+
if (compare_events(options.version?.[0], meta.event) > 0)
|
|
220
|
+
throw new Error('unknown version: ' + options.version)
|
|
221
|
+
if (compare_events(options.parents?.[0], meta.event) > 0)
|
|
222
|
+
throw new Error('unknown version: ' + options.parents)
|
|
223
|
+
}
|
|
224
|
+
if (options.head) return result
|
|
225
|
+
|
|
226
|
+
if (options.subscribe) {
|
|
227
|
+
var subscribe_chain = Promise.resolve()
|
|
228
|
+
options.my_subscribe = (x) => subscribe_chain =
|
|
229
|
+
subscribe_chain.then(() =>
|
|
230
|
+
!options.signal?.aborted && options.subscribe(x))
|
|
231
|
+
|
|
232
|
+
// Start a subscription for future updates
|
|
233
|
+
if (!braid_blob.key_to_subs[key])
|
|
234
|
+
braid_blob.key_to_subs[key] = new Map()
|
|
235
|
+
|
|
236
|
+
var peer = options.peer || Math.random().toString(36).slice(2)
|
|
237
|
+
braid_blob.key_to_subs[key].set(peer, {
|
|
238
|
+
sendUpdate: (update) => {
|
|
239
|
+
if (update.delete) options.my_subscribe(update)
|
|
240
|
+
else if (compare_events(update.version[0], options.parents?.[0]) > 0)
|
|
241
|
+
options.my_subscribe(update)
|
|
242
|
+
}
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
options.signal?.addEventListener('abort', () => {
|
|
246
|
+
braid_blob.key_to_subs[key].delete(peer)
|
|
247
|
+
if (!braid_blob.key_to_subs[key].size)
|
|
248
|
+
delete braid_blob.key_to_subs[key]
|
|
261
249
|
})
|
|
250
|
+
|
|
251
|
+
if (options.before_send_cb) await options.before_send_cb()
|
|
252
|
+
if (options.signal?.aborted) return
|
|
253
|
+
|
|
254
|
+
// Send an immediate update if needed
|
|
255
|
+
if (compare_events(result.version?.[0], options.parents?.[0]) > 0) {
|
|
256
|
+
result.sent = true
|
|
257
|
+
result.body = await (options.db || braid_blob.db).read(key)
|
|
258
|
+
options.my_subscribe(result)
|
|
259
|
+
}
|
|
260
|
+
} else {
|
|
261
|
+
// If not subscribe, send the body now
|
|
262
|
+
result.body = await (options.db || braid_blob.db).read(key)
|
|
262
263
|
}
|
|
263
|
-
} else {
|
|
264
|
-
// If not subscribe, send the body now
|
|
265
|
-
result.body = await braid_blob.db.read(key)
|
|
266
|
-
}
|
|
267
264
|
|
|
268
|
-
|
|
265
|
+
return result
|
|
266
|
+
})
|
|
269
267
|
}
|
|
270
268
|
|
|
271
269
|
braid_blob.delete = async (key, options = {}) => {
|
|
@@ -273,13 +271,18 @@ function create_braid_blob() {
|
|
|
273
271
|
|
|
274
272
|
// Handle URL case - make a remote DELETE request
|
|
275
273
|
if (key instanceof URL) {
|
|
276
|
-
|
|
277
274
|
var params = {
|
|
278
275
|
method: 'DELETE',
|
|
279
276
|
signal: options.signal
|
|
280
277
|
}
|
|
278
|
+
if (!options.dont_retry)
|
|
279
|
+
params.retry = (res) => res.status !== 309 &&
|
|
280
|
+
res.status !== 404 && res.status !== 406
|
|
281
281
|
for (var x of ['headers', 'peer'])
|
|
282
282
|
if (options[x] != null) params[x] = options[x]
|
|
283
|
+
if (options.content_type)
|
|
284
|
+
params.headers = { ...params.headers,
|
|
285
|
+
'Accept': options.content_type }
|
|
283
286
|
|
|
284
287
|
return await braid_fetch(key.href, params)
|
|
285
288
|
}
|
|
@@ -287,14 +290,24 @@ function create_braid_blob() {
|
|
|
287
290
|
await braid_blob.init()
|
|
288
291
|
if (options.signal?.aborted) return
|
|
289
292
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
+
return await within_fiber(key, async () => {
|
|
294
|
+
var meta = await get_meta(key)
|
|
295
|
+
if (options.signal?.aborted) return
|
|
296
|
+
|
|
297
|
+
await (options.db || braid_blob.db).delete(key)
|
|
298
|
+
await delete_meta(key)
|
|
293
299
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
300
|
+
// Notify all subscriptions of the delete
|
|
301
|
+
// (except the peer which made the DELETE request itself)
|
|
302
|
+
var update = {
|
|
303
|
+
delete: true,
|
|
304
|
+
content_type: meta.content_type
|
|
305
|
+
}
|
|
306
|
+
if (braid_blob.key_to_subs[key])
|
|
307
|
+
for (var [peer, sub] of braid_blob.key_to_subs[key].entries())
|
|
308
|
+
if (!options.peer || options.peer !== peer)
|
|
309
|
+
sub.sendUpdate(update)
|
|
310
|
+
})
|
|
298
311
|
}
|
|
299
312
|
|
|
300
313
|
braid_blob.serve = async (req, res, options = {}) => {
|
|
@@ -309,82 +322,85 @@ function create_braid_blob() {
|
|
|
309
322
|
if (res.is_multiplexer) return
|
|
310
323
|
|
|
311
324
|
// Handle OPTIONS request
|
|
312
|
-
if (req.method === 'OPTIONS') return res.end()
|
|
325
|
+
if (req.method === 'OPTIONS') return res.end()
|
|
313
326
|
|
|
314
327
|
// consume PUT body
|
|
315
328
|
var body = req.method === 'PUT' && await slurp(req)
|
|
316
329
|
|
|
317
|
-
|
|
318
|
-
if (
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
res.setHeader("Merge-Type", "aww")
|
|
330
|
+
if (req.method === 'GET' || req.method === 'HEAD') {
|
|
331
|
+
if (!res.hasHeader("editable")) res.setHeader("Editable", "true")
|
|
332
|
+
if (!req.subscribe) res.setHeader("Accept-Subscribe", "true")
|
|
333
|
+
res.setHeader("Merge-Type", "aww")
|
|
322
334
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
335
|
+
try {
|
|
336
|
+
var result = await braid_blob.get(options.key, {
|
|
337
|
+
peer: req.peer,
|
|
338
|
+
head: req.method === "HEAD",
|
|
339
|
+
version: req.version,
|
|
340
|
+
parents: req.parents,
|
|
341
|
+
header_cb: (result) => {
|
|
342
|
+
res.setHeader((req.subscribe ? "Current-" : "") +
|
|
343
|
+
"Version", version_to_header(result.version))
|
|
344
|
+
if (result.content_type)
|
|
345
|
+
res.setHeader('Content-Type', result.content_type)
|
|
346
|
+
},
|
|
347
|
+
before_send_cb: () => res.startSubscription(),
|
|
348
|
+
subscribe: req.subscribe ? (update) => {
|
|
349
|
+
if (update.delete) {
|
|
350
|
+
update.status = 404
|
|
351
|
+
delete update.delete
|
|
352
|
+
}
|
|
353
|
+
if (update.content_type) {
|
|
354
|
+
update['Content-Type'] = update.content_type
|
|
355
|
+
delete update.content_type
|
|
356
|
+
}
|
|
357
|
+
update['Merge-Type'] = 'aww'
|
|
358
|
+
res.sendUpdate(update)
|
|
359
|
+
} : null
|
|
360
|
+
})
|
|
361
|
+
} catch (e) {
|
|
362
|
+
if (e.message && e.message.startsWith('unknown version')) {
|
|
363
|
+
// Server doesn't have this version
|
|
364
|
+
res.statusCode = 309
|
|
365
|
+
res.statusMessage = 'Version Unknown Here'
|
|
366
|
+
return res.end('')
|
|
367
|
+
} else throw e
|
|
368
|
+
}
|
|
354
369
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
370
|
+
if (!result) {
|
|
371
|
+
res.statusCode = 404
|
|
372
|
+
return res.end('File Not Found')
|
|
373
|
+
}
|
|
359
374
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
375
|
+
if (result.content_type && req.headers.accept &&
|
|
376
|
+
!isAcceptable(result.content_type, req.headers.accept)) {
|
|
377
|
+
res.statusCode = 406
|
|
378
|
+
return res.end(`Content-Type of ${result.content_type} not in Accept: ${req.headers.accept}`)
|
|
379
|
+
}
|
|
365
380
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
}
|
|
373
|
-
} else if (req.method === 'PUT') {
|
|
374
|
-
// Handle PUT request to update binary files
|
|
375
|
-
var event = await braid_blob.put(options.key, body, {
|
|
376
|
-
version: req.version,
|
|
377
|
-
content_type: req.headers['content-type'],
|
|
378
|
-
peer: req.peer
|
|
379
|
-
})
|
|
380
|
-
res.setHeader("Version", version_to_header(event != null ? [event] : []))
|
|
381
|
-
res.end('')
|
|
382
|
-
} else if (req.method === 'DELETE') {
|
|
383
|
-
await braid_blob.delete(options.key)
|
|
384
|
-
res.statusCode = 204 // No Content
|
|
385
|
-
res.end('')
|
|
381
|
+
if (req.method == "HEAD") return res.end('')
|
|
382
|
+
else if (!req.subscribe) return res.end(result.body)
|
|
383
|
+
else {
|
|
384
|
+
// If no immediate update was sent,
|
|
385
|
+
// get the node http code to send headers
|
|
386
|
+
if (!result.sent) res.write('\n\n')
|
|
386
387
|
}
|
|
387
|
-
})
|
|
388
|
+
} else if (req.method === 'PUT') {
|
|
389
|
+
// Handle PUT request to update binary files
|
|
390
|
+
var event = await braid_blob.put(options.key, body, {
|
|
391
|
+
version: req.version,
|
|
392
|
+
content_type: req.headers['content-type'],
|
|
393
|
+
peer: req.peer
|
|
394
|
+
})
|
|
395
|
+
res.setHeader("Version", version_to_header(event != null ? [event] : []))
|
|
396
|
+
res.end('')
|
|
397
|
+
} else if (req.method === 'DELETE') {
|
|
398
|
+
await braid_blob.delete(options.key, {
|
|
399
|
+
content_type: req.headers['content-type'],
|
|
400
|
+
peer: req.peer
|
|
401
|
+
})
|
|
402
|
+
res.end('')
|
|
403
|
+
}
|
|
388
404
|
}
|
|
389
405
|
|
|
390
406
|
braid_blob.sync = (a, b, options = {}) => {
|
|
@@ -392,173 +408,131 @@ function create_braid_blob() {
|
|
|
392
408
|
if (!options.peer) options.peer = Math.random().toString(36).slice(2)
|
|
393
409
|
|
|
394
410
|
if ((a instanceof URL) === (b instanceof URL)) {
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
peer: options.peer,
|
|
405
|
-
subscribe: update => {
|
|
406
|
-
braid_blob.put(b, update.body, {
|
|
407
|
-
signal: options.signal,
|
|
411
|
+
braid_blob.get(a, {
|
|
412
|
+
...options,
|
|
413
|
+
subscribe: async update => {
|
|
414
|
+
if (update.delete) await braid_blob.delete(b, {
|
|
415
|
+
...options,
|
|
416
|
+
content_type: update.content_type,
|
|
417
|
+
})
|
|
418
|
+
else await braid_blob.put(b, update.body, {
|
|
419
|
+
...options,
|
|
408
420
|
version: update.version,
|
|
409
|
-
headers: options.headers,
|
|
410
421
|
content_type: update.content_type,
|
|
411
|
-
|
|
412
|
-
}).then(a_first_put)
|
|
422
|
+
})
|
|
413
423
|
}
|
|
414
|
-
}
|
|
415
|
-
braid_blob.get(
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
subscribe: update => {
|
|
425
|
-
braid_blob.put(a, update.body, {
|
|
426
|
-
signal: options.signal,
|
|
424
|
+
})
|
|
425
|
+
braid_blob.get(b, {
|
|
426
|
+
...options,
|
|
427
|
+
subscribe: async update => {
|
|
428
|
+
if (update.delete) await braid_blob.delete(a, {
|
|
429
|
+
...options,
|
|
430
|
+
content_type: update.content_type,
|
|
431
|
+
})
|
|
432
|
+
else await braid_blob.put(a, update.body, {
|
|
433
|
+
...options,
|
|
427
434
|
version: update.version,
|
|
428
|
-
headers: options.headers,
|
|
429
435
|
content_type: update.content_type,
|
|
430
|
-
|
|
431
|
-
}).then(b_first_put)
|
|
436
|
+
})
|
|
432
437
|
}
|
|
433
|
-
}
|
|
434
|
-
braid_blob.get(b, b_ops).then(x =>
|
|
435
|
-
x || a_first_put_promise.then(() =>
|
|
436
|
-
braid_blob.get(b, b_ops)))
|
|
438
|
+
})
|
|
437
439
|
} else {
|
|
438
440
|
// One is local, one is remote - make a=local and b=remote (swap if not)
|
|
439
441
|
if (a instanceof URL) {
|
|
440
442
|
let swap = a; a = b; b = swap
|
|
441
443
|
}
|
|
442
444
|
|
|
443
|
-
var
|
|
444
|
-
|
|
445
|
-
options.signal?.addEventListener('abort', () =>
|
|
446
|
-
{ closed = true; disconnect() })
|
|
447
|
-
|
|
448
|
-
var local_first_put, remote_first_put
|
|
449
|
-
var local_first_put_promise = new Promise(done => local_first_put = done)
|
|
450
|
-
var remote_first_put_promise = new Promise(done => remote_first_put = done)
|
|
445
|
+
var ac = new AbortController()
|
|
446
|
+
options.signal?.addEventListener('abort', () => ac.abort())
|
|
451
447
|
|
|
452
448
|
function handle_error(e) {
|
|
453
|
-
if (
|
|
454
|
-
disconnect()
|
|
449
|
+
if (ac.signal.aborted) return
|
|
455
450
|
console.log(`disconnected, retrying in 1 second`)
|
|
456
451
|
setTimeout(connect, 1000)
|
|
457
452
|
}
|
|
458
453
|
|
|
459
454
|
async function connect() {
|
|
455
|
+
if (ac.signal.aborted) return
|
|
460
456
|
if (options.on_pre_connect) await options.on_pre_connect()
|
|
461
457
|
|
|
462
|
-
var ac = new AbortController()
|
|
463
|
-
disconnect = () => ac.abort()
|
|
464
|
-
|
|
465
458
|
try {
|
|
466
459
|
// Check if remote has our current version (simple fork-point check)
|
|
467
|
-
var local_result = await braid_blob.get(a, { head: true })
|
|
468
|
-
var local_version = local_result ? local_result.version : null
|
|
469
460
|
var server_has_our_version = false
|
|
470
|
-
|
|
461
|
+
var local_version = (await braid_blob.get(a, {
|
|
462
|
+
...options,
|
|
463
|
+
signal: ac.signal,
|
|
464
|
+
head: true
|
|
465
|
+
}))?.version
|
|
471
466
|
if (local_version) {
|
|
472
467
|
var r = await braid_blob.get(b, {
|
|
468
|
+
...options,
|
|
473
469
|
signal: ac.signal,
|
|
474
470
|
head: true,
|
|
475
471
|
dont_retry: true,
|
|
476
472
|
version: local_version,
|
|
477
|
-
headers: options.headers,
|
|
478
|
-
content_type: options.content_type,
|
|
479
|
-
peer: options.peer,
|
|
480
473
|
})
|
|
481
474
|
server_has_our_version = !!r
|
|
482
475
|
}
|
|
483
476
|
|
|
484
|
-
// Local -> remote
|
|
485
|
-
|
|
477
|
+
// Local -> remote
|
|
478
|
+
await braid_blob.get(a, {
|
|
479
|
+
...options,
|
|
486
480
|
signal: ac.signal,
|
|
487
|
-
|
|
488
|
-
content_type: options.content_type,
|
|
489
|
-
peer: options.peer,
|
|
481
|
+
parents: server_has_our_version ? local_version : null,
|
|
490
482
|
subscribe: async update => {
|
|
491
483
|
try {
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
484
|
+
if (update.delete) {
|
|
485
|
+
var x = await braid_blob.delete(b, {
|
|
486
|
+
...options,
|
|
487
|
+
signal: ac.signal,
|
|
488
|
+
dont_retry: true,
|
|
489
|
+
content_type: update.content_type,
|
|
490
|
+
})
|
|
491
|
+
if (!x.ok) handle_error(new Error('failed to delete'))
|
|
492
|
+
} else {
|
|
493
|
+
var x = await braid_blob.put(b, update.body, {
|
|
494
|
+
...options,
|
|
495
|
+
signal: ac.signal,
|
|
496
|
+
dont_retry: true,
|
|
497
|
+
version: update.version,
|
|
498
|
+
content_type: update.content_type,
|
|
499
|
+
})
|
|
500
|
+
if ((x.status === 401 || x.status === 403) && options.on_unauthorized) {
|
|
501
|
+
await options.on_unauthorized?.()
|
|
502
|
+
} else if (!x.ok) handle_error(new Error('failed to PUT: ' + x.status))
|
|
503
|
+
}
|
|
504
504
|
} catch (e) {
|
|
505
|
-
if (e.name !== 'AbortError')
|
|
505
|
+
if (e.name !== 'AbortError')
|
|
506
|
+
handle_error(e)
|
|
506
507
|
}
|
|
507
508
|
}
|
|
508
|
-
}
|
|
509
|
-
// Only set parents if server already has our version
|
|
510
|
-
// If server doesn't have it, omit parents so subscription sends everything
|
|
511
|
-
if (server_has_our_version) {
|
|
512
|
-
a_ops.parents = local_version
|
|
513
|
-
}
|
|
509
|
+
})
|
|
514
510
|
|
|
515
|
-
// Remote -> local
|
|
516
|
-
var
|
|
511
|
+
// Remote -> local
|
|
512
|
+
var remote_res = await braid_blob.get(b, {
|
|
513
|
+
...options,
|
|
517
514
|
signal: ac.signal,
|
|
518
515
|
dont_retry: true,
|
|
519
|
-
|
|
520
|
-
content_type: options.content_type,
|
|
521
|
-
peer: options.peer,
|
|
516
|
+
parents: local_version,
|
|
522
517
|
subscribe: async update => {
|
|
523
|
-
await braid_blob.
|
|
518
|
+
if (update.delete) await braid_blob.delete(a, {
|
|
519
|
+
...options,
|
|
520
|
+
signal: ac.signal,
|
|
521
|
+
content_type: update.content_type,
|
|
522
|
+
})
|
|
523
|
+
else await braid_blob.put(a, update.body, {
|
|
524
|
+
...options,
|
|
525
|
+
signal: ac.signal,
|
|
524
526
|
version: update.version,
|
|
525
|
-
headers: options.headers,
|
|
526
527
|
content_type: update.content_type,
|
|
527
|
-
peer: options.peer,
|
|
528
528
|
})
|
|
529
|
-
remote_first_put()
|
|
530
529
|
},
|
|
531
530
|
on_error: e => {
|
|
532
531
|
options.on_disconnect?.()
|
|
533
532
|
handle_error(e)
|
|
534
533
|
}
|
|
535
|
-
}
|
|
536
|
-
// Use fork-point (parents) to avoid receiving data we already have
|
|
537
|
-
if (local_version) {
|
|
538
|
-
b_ops.parents = local_version
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
// Set up both subscriptions, handling cases where one doesn't exist yet
|
|
542
|
-
braid_blob.get(a, a_ops).then(x =>
|
|
543
|
-
x || remote_first_put_promise.then(async () => {
|
|
544
|
-
// update parents, since we know remote has the version we just got from them..
|
|
545
|
-
var local_result = await braid_blob.get(a, { head: true })
|
|
546
|
-
a_ops.parents = local_result.version
|
|
547
|
-
braid_blob.get(a, a_ops)
|
|
548
|
-
}))
|
|
549
|
-
|
|
550
|
-
var remote_res = await braid_blob.get(b, b_ops)
|
|
551
|
-
|
|
552
|
-
// If remote doesn't exist yet, wait for it to be created then reconnect
|
|
553
|
-
if (!remote_res) {
|
|
554
|
-
await local_first_put_promise
|
|
555
|
-
disconnect()
|
|
556
|
-
connect()
|
|
557
|
-
}
|
|
558
|
-
|
|
534
|
+
})
|
|
559
535
|
options.on_res?.(remote_res)
|
|
560
|
-
|
|
561
|
-
// Otherwise, on_error will call handle_error when connection drops
|
|
562
536
|
} catch (e) {
|
|
563
537
|
handle_error(e)
|
|
564
538
|
}
|
|
@@ -568,24 +542,55 @@ function create_braid_blob() {
|
|
|
568
542
|
}
|
|
569
543
|
|
|
570
544
|
function compare_events(a, b) {
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
var c = a_num.length - b_num.length
|
|
575
|
-
if (c) return c
|
|
545
|
+
if (!a) a = ''
|
|
546
|
+
if (!b) b = ''
|
|
576
547
|
|
|
577
|
-
var c =
|
|
548
|
+
var c = compare_seqs(get_event_seq(a), get_event_seq(b))
|
|
578
549
|
if (c) return c
|
|
579
550
|
|
|
580
|
-
|
|
551
|
+
if (a < b) return -1
|
|
552
|
+
if (a > b) return 1
|
|
553
|
+
return 0
|
|
581
554
|
}
|
|
582
555
|
|
|
583
556
|
function get_event_seq(e) {
|
|
557
|
+
if (!e) return ''
|
|
558
|
+
|
|
584
559
|
for (let i = e.length - 1; i >= 0; i--)
|
|
585
560
|
if (e[i] === '-') return e.slice(i + 1)
|
|
586
561
|
return e
|
|
587
562
|
}
|
|
588
563
|
|
|
564
|
+
function increment_seq(s) {
|
|
565
|
+
if (!s) return '1'
|
|
566
|
+
|
|
567
|
+
let last = s[s.length - 1]
|
|
568
|
+
let rest = s.slice(0, -1)
|
|
569
|
+
|
|
570
|
+
if (last >= '0' && last <= '8')
|
|
571
|
+
return rest + String.fromCharCode(last.charCodeAt(0) + 1)
|
|
572
|
+
else
|
|
573
|
+
return increment_seq(rest) + '0'
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function max_seq(a, b) {
|
|
577
|
+
if (!a) a = ''
|
|
578
|
+
if (!b) b = ''
|
|
579
|
+
|
|
580
|
+
if (compare_seqs(a, b) > 0) return a
|
|
581
|
+
return b
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function compare_seqs(a, b) {
|
|
585
|
+
if (!a) a = ''
|
|
586
|
+
if (!b) b = ''
|
|
587
|
+
|
|
588
|
+
if (a.length !== b.length) return a.length - b.length
|
|
589
|
+
if (a < b) return -1
|
|
590
|
+
if (a > b) return 1
|
|
591
|
+
return 0
|
|
592
|
+
}
|
|
593
|
+
|
|
589
594
|
function ascii_ify(s) {
|
|
590
595
|
return s.replace(/[^\x20-\x7E]/g, c => '\\u' + c.charCodeAt(0).toString(16).padStart(4, '0'))
|
|
591
596
|
}
|