braid-blob 0.0.13 → 0.0.14

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 CHANGED
@@ -84,8 +84,9 @@ braid_blob.serve = async (req, res, options = {}) => {
84
84
  our_v > 1*req.parents[0] // 3) Or our version is newer
85
85
  )
86
86
  return res.sendUpdate({
87
- version: our_v != null ? ['' + our_v] : [],
88
- body: our_v != null ? await fs.promises.readFile(filename) : ''
87
+ version: ['' + our_v],
88
+ 'Merge-Type': 'lww',
89
+ body: await fs.promises.readFile(filename)
89
90
  })
90
91
  else res.write('\n\n') // get the node http code to send headers
91
92
  } else if (req.method === 'PUT') {
@@ -120,7 +121,11 @@ braid_blob.serve = async (req, res, options = {}) => {
120
121
  if (key_to_subs[options.key])
121
122
  for (var [peer, sub] of key_to_subs[options.key].entries())
122
123
  if (peer !== req.peer)
123
- sub.sendUpdate({ body, version: ['' + their_v] })
124
+ sub.sendUpdate({
125
+ version: ['' + their_v],
126
+ 'Merge-Type': 'lww',
127
+ body
128
+ })
124
129
 
125
130
  res.setHeader("Version", `"${their_v}"`)
126
131
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "braid-blob",
3
- "version": "0.0.13",
3
+ "version": "0.0.14",
4
4
  "description": "Library for collaborative blobs over http using braid.",
5
5
  "author": "Braid Working Group",
6
6
  "repository": "braid-org/braid-blob",
package/test/server.js ADDED
@@ -0,0 +1,44 @@
1
+
2
+ var port = process.argv[2] || 8889
3
+
4
+ var braid_blob = require(`${__dirname}/../index.js`)
5
+ var {free_cors} = require("braid-http")
6
+ braid_blob.db_folder = `${__dirname}/test_db_folder`
7
+ braid_blob.meta_folder = `${__dirname}/test_meta_folder`
8
+
9
+ var server = require("http").createServer(async (req, res) => {
10
+ console.log(`${req.method} ${req.url}`)
11
+
12
+ // Free the CORS
13
+ free_cors(res)
14
+ if (req.method === 'OPTIONS') return
15
+
16
+ if (req.url.startsWith('/eval')) {
17
+ var body = await new Promise(done => {
18
+ var chunks = []
19
+ req.on('data', chunk => chunks.push(chunk))
20
+ req.on('end', () => done(Buffer.concat(chunks)))
21
+ })
22
+ try {
23
+ eval('' + body)
24
+ } catch (error) {
25
+ res.writeHead(500, { 'Content-Type': 'text/plain' })
26
+ res.end(`Error: ${error.message}`)
27
+ }
28
+ return
29
+ }
30
+
31
+ if (req.url.startsWith('/test.html')) {
32
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-cache" })
33
+ require("fs").createReadStream(`${__dirname}/test.html`).pipe(res)
34
+ return
35
+ }
36
+
37
+ // Now serve the collaborative text!
38
+ braid_blob.serve(req, res)
39
+ })
40
+
41
+ // only listen on 'localhost' for security
42
+ server.listen(port, 'localhost', () => {
43
+ console.log(`serving: http://localhost:${port}/test.html`)
44
+ })
package/test/test.html ADDED
@@ -0,0 +1,534 @@
1
+ <style>
2
+ body {
3
+ font-family: Arial, sans-serif;
4
+ max-width: 800px;
5
+ margin: 0 auto;
6
+ padding: 10px;
7
+ }
8
+ .test {
9
+ margin-bottom: 3px;
10
+ padding: 3px;
11
+ }
12
+ .running {
13
+ background-color: #fffde7;
14
+ }
15
+ .passed {
16
+ background-color: #e8f5e9;
17
+ }
18
+ .failed {
19
+ background-color: #ffebee;
20
+ }
21
+ #summaryContainer {
22
+ display: flex;
23
+ flex-wrap: wrap;
24
+ gap: 5px;
25
+ margin-bottom: 20px;
26
+ }
27
+ .summaryBox {
28
+ width: 25px;
29
+ height: 25px;
30
+ border: 1px solid #ddd;
31
+ }
32
+ </style>
33
+ <script src="https://unpkg.com/braid-http@~1.3/braid-http-client.js"></script>
34
+ <div id="summaryContainer"></div>
35
+ <div id="testContainer"></div>
36
+ <script type=module>
37
+
38
+ let delay = 0
39
+
40
+ function createTestDiv(testName) {
41
+ const div = document.createElement("div")
42
+ div.className = "test running"
43
+ div.innerHTML = `<span style="font-weight:bold">${testName}: </span><span class="result">Running...</span>`
44
+ testContainer.appendChild(div)
45
+ return div
46
+ }
47
+
48
+ function updateTestResult(div, passed, message, got, expected) {
49
+ div.className = `test ${passed ? "passed" : "failed"}`
50
+
51
+ if (passed) {
52
+ div.querySelector(".result").textContent = message
53
+ div.querySelector(".result").style.fontSize = message.length > 400 ? 'xx-small' : message.length > 100 ? 'small' : ''
54
+ } else {
55
+ div.querySelector(".result").innerHTML = `${message}<br><strong>Got:</strong> ${got}<br><strong>Expected:</strong> ${expected}`
56
+ }
57
+ }
58
+
59
+ function createSummaryBox() {
60
+ var summaryContainer = document.getElementById('summaryContainer')
61
+ const box = document.createElement('div');
62
+ box.className = 'summaryBox running';
63
+ summaryContainer.appendChild(box);
64
+ return box;
65
+ }
66
+
67
+ function updateSummaryBox(box, passed) {
68
+ box.className = `summaryBox ${passed ? 'passed' : passed === false ? 'failed' : 'other'}`;
69
+ }
70
+
71
+ async function runTest(testName, testFunction, expectedResult) {
72
+ delay += 70
73
+
74
+ await new Promise(done => setTimeout(done, delay))
75
+ const div = createTestDiv(testName)
76
+ const summaryBox = createSummaryBox()
77
+ try {
78
+ let x = await testFunction()
79
+ if (x == expectedResult) {
80
+ updateTestResult(div, true, x)
81
+ updateSummaryBox(summaryBox, true)
82
+ } else {
83
+ updateTestResult(div, false, "Mismatch:", x, expectedResult)
84
+ updateSummaryBox(summaryBox, false)
85
+ }
86
+ } catch (error) {
87
+ updateTestResult(div, false, "Error:", error.message || error, expectedResult)
88
+ updateSummaryBox(summaryBox, false)
89
+ }
90
+ }
91
+
92
+ runTest(
93
+ "test send an update to another peer",
94
+ async () => {
95
+ var key = 'test-' + Math.random().toString(36).slice(2)
96
+
97
+ var r = await braid_fetch(`/${key}`, {
98
+ method: 'PUT',
99
+ headers: {'Content-Type': 'text/plain'},
100
+ version: ['1'],
101
+ body: 'xyz'
102
+ })
103
+ if (!r.ok) throw 'got: ' + r.statusCode
104
+
105
+ var a = new AbortController()
106
+ var r = await braid_fetch(`/${key}`, {
107
+ signal: a.signal,
108
+ subscribe: true,
109
+ peer: key
110
+ })
111
+
112
+ var p = new Promise(done => {
113
+ r.subscribe(update => {
114
+ if (update.version?.[0] !== '2') return
115
+ done(update.body_text)
116
+ a.abort()
117
+ })
118
+ })
119
+
120
+ var r = await braid_fetch(`/${key}`, {
121
+ method: 'PUT',
122
+ headers: {'Content-Type': 'text/plain'},
123
+ version: ['2'],
124
+ body: 'abc'
125
+ })
126
+ if (!r.ok) throw 'got: ' + r.statusCode
127
+
128
+ return await p
129
+ },
130
+ 'abc'
131
+ )
132
+
133
+ runTest(
134
+ "test getting a 406",
135
+ async () => {
136
+ var key = 'test-' + Math.random().toString(36).slice(2)
137
+
138
+ var r = await braid_fetch(`/${key}`, {
139
+ method: 'PUT',
140
+ headers: {'Content-Type': 'text/plain'},
141
+ version: ['1'],
142
+ parents: [],
143
+ body: 'xyz'
144
+ })
145
+ if (!r.ok) throw 'got: ' + r.statusCode
146
+
147
+ var r = await braid_fetch(`/${key}`, {
148
+ headers: {Accept: 'text/html'}
149
+ })
150
+ return r.status + ' ' + await r.text()
151
+ },
152
+ '406 Content-Type of text/plain not in Accept: text/html'
153
+ )
154
+
155
+ runTest(
156
+ "test deleting something",
157
+ async () => {
158
+ var key = 'test-' + Math.random().toString(36).slice(2)
159
+
160
+ var r = await braid_fetch(`/${key}`, {
161
+ method: 'PUT',
162
+ version: ['1'],
163
+ parents: [],
164
+ body: 'xyz'
165
+ })
166
+ if (!r.ok) throw 'got: ' + r.statusCode
167
+
168
+ var r = await braid_fetch(`/${key}`, {
169
+ method: 'DELETE',
170
+ })
171
+ if (!r.ok) throw 'got: ' + r.statusCode
172
+
173
+ var r = await braid_fetch(`/${key}`)
174
+ return r.status
175
+ },
176
+ '404'
177
+ )
178
+
179
+ runTest(
180
+ "test deleting something that doesn't exist",
181
+ async () => {
182
+ var key = 'test-' + Math.random().toString(36).slice(2)
183
+
184
+ var r = await braid_fetch(`/${key}`, {
185
+ method: 'DELETE',
186
+ })
187
+ if (!r.ok) throw 'got: ' + r.statusCode
188
+
189
+ return r.status
190
+ },
191
+ '204'
192
+ )
193
+
194
+ runTest(
195
+ "test that subscribe returns current-version header",
196
+ async () => {
197
+ var key = 'test-' + Math.random().toString(36).slice(2)
198
+
199
+ var r = await braid_fetch(`/${key}`, {
200
+ method: 'PUT',
201
+ version: ['1'],
202
+ parents: [],
203
+ body: 'xyz'
204
+ })
205
+ if (!r.ok) throw 'got: ' + r.statusCode
206
+
207
+ var a = new AbortController()
208
+ var r = await braid_fetch(`/${key}`, {
209
+ signal: a.signal,
210
+ subscribe: true
211
+ })
212
+ a.abort()
213
+ return r.headers.get('current-version')
214
+ },
215
+ '"1"'
216
+ )
217
+
218
+ runTest(
219
+ "test that subscribe returns version as string-number in array",
220
+ async () => {
221
+ var key = 'test-' + Math.random().toString(36).slice(2)
222
+
223
+ var r = await braid_fetch(`/${key}`, {
224
+ method: 'PUT',
225
+ version: ['1'],
226
+ parents: [],
227
+ body: 'xyz'
228
+ })
229
+ if (!r.ok) throw 'got: ' + r.statusCode
230
+
231
+ var a = new AbortController()
232
+ var r = await braid_fetch(`/${key}`, {
233
+ signal: a.signal,
234
+ subscribe: true
235
+ })
236
+
237
+ var x = await new Promise(done => {
238
+ r.subscribe(update => {
239
+ done(update.version)
240
+ })
241
+ })
242
+
243
+ a.abort()
244
+ return JSON.stringify(x)
245
+ },
246
+ '["1"]'
247
+ )
248
+
249
+ runTest(
250
+ "test that subscribe's update's versions are string-number in array",
251
+ async () => {
252
+ var key = 'test-' + Math.random().toString(36).slice(2)
253
+
254
+ var r = await braid_fetch(`/${key}`, {
255
+ method: 'PUT',
256
+ version: ['4'],
257
+ body: 'xyz'
258
+ })
259
+ if (!r.ok) throw 'got: ' + r.statusCode
260
+
261
+ var a = new AbortController()
262
+ var r = await braid_fetch(`/${key}`, {
263
+ signal: a.signal,
264
+ subscribe: true,
265
+ parents: ['0']
266
+ })
267
+
268
+ var p = new Promise(done => {
269
+ r.subscribe(update => {
270
+ done(update.version)
271
+ })
272
+ })
273
+
274
+ var ret = await p
275
+ a.abort()
276
+ return JSON.stringify(ret)
277
+ },
278
+ '["4"]'
279
+ )
280
+
281
+ runTest(
282
+ "test that non-subscribe returns version header",
283
+ async () => {
284
+ var key = 'test-' + Math.random().toString(36).slice(2)
285
+
286
+ var r = await braid_fetch(`/${key}`, {
287
+ method: 'PUT',
288
+ version: ['2'],
289
+ parents: [],
290
+ body: 'xyz'
291
+ })
292
+ if (!r.ok) throw 'got: ' + r.statusCode
293
+
294
+ var r = await braid_fetch(`/${key}`)
295
+ return r.headers.get('version')
296
+ },
297
+ '"2"'
298
+ )
299
+
300
+ runTest(
301
+ "test that PUTing at version [] doesn't do anything.",
302
+ async () => {
303
+ var key = 'test-' + Math.random().toString(36).slice(2)
304
+
305
+ var r = await braid_fetch(`/${key}`, {
306
+ method: 'PUT',
307
+ version: [],
308
+ parents: [],
309
+ body: 'xyz'
310
+ })
311
+ if (!r.ok) throw 'got: ' + r.statusCode
312
+
313
+ var r = await braid_fetch(`/${key}`)
314
+ return r.status
315
+ },
316
+ '404'
317
+ )
318
+
319
+ runTest(
320
+ "test that subscribe sends no version if parents is big enough.",
321
+ async () => {
322
+ var key = 'test-' + Math.random().toString(36).slice(2)
323
+
324
+ var r = await braid_fetch(`/${key}`, {
325
+ method: 'PUT',
326
+ version: ['3'],
327
+ parents: [],
328
+ body: 'xyz'
329
+ })
330
+ if (!r.ok) throw 'got: ' + r.statusCode
331
+
332
+ var a = new AbortController()
333
+ var r = await braid_fetch(`/${key}`, {
334
+ signal: a.signal,
335
+ subscribe: true,
336
+ parents: ['3']
337
+ })
338
+
339
+ var received_update = false
340
+ var promise_a = new Promise(done => {
341
+ r.subscribe(async (update) => {
342
+ received_update = true
343
+ done()
344
+ })
345
+ })
346
+
347
+ var promise_b = new Promise(done => setTimeout(done, 300))
348
+
349
+ await Promise.race([promise_a, promise_b])
350
+ a.abort()
351
+
352
+ return '' + received_update
353
+ },
354
+ 'false'
355
+ )
356
+
357
+ runTest(
358
+ "test that subscribe sends 404 if there is no file.",
359
+ async () => {
360
+ var key = 'test-' + Math.random().toString(36).slice(2)
361
+
362
+ var r = await braid_fetch(`/${key}`, {
363
+ subscribe: true,
364
+ })
365
+ return r.status
366
+ },
367
+ '404'
368
+ )
369
+
370
+ runTest(
371
+ "test that we get 404 when file doesn't exist, on GET without subscribe.",
372
+ async () => {
373
+ var key = 'test-' + Math.random().toString(36).slice(2)
374
+
375
+ var r = await braid_fetch(`/${key}`)
376
+
377
+ return `${r.status}`
378
+ },
379
+ '404'
380
+ )
381
+
382
+ runTest(
383
+ "test second subscription to same key",
384
+ async () => {
385
+ var key = 'test-' + Math.random().toString(36).slice(2)
386
+
387
+ var r = await braid_fetch(`/${key}`, {
388
+ method: 'PUT',
389
+ version: ['3'],
390
+ parents: [],
391
+ body: 'xyz'
392
+ })
393
+ if (!r.ok) throw 'got: ' + r.statusCode
394
+
395
+ var a = new AbortController()
396
+ var r = await braid_fetch(`/${key}`, {
397
+ signal: a.signal,
398
+ subscribe: true,
399
+ parents: ['3']
400
+ })
401
+
402
+ var a2 = new AbortController()
403
+ var r2 = await braid_fetch(`/${key}`, {
404
+ signal: a2.signal,
405
+ subscribe: true,
406
+ parents: ['2']
407
+ })
408
+
409
+ var body = await new Promise(done => {
410
+ r2.subscribe((update) => done(update.body_text))
411
+ })
412
+
413
+ a.abort()
414
+ a2.abort()
415
+ return body
416
+ },
417
+ 'xyz'
418
+ )
419
+
420
+ runTest(
421
+ "test PUTing when server already has blob",
422
+ async () => {
423
+ var key = 'test-' + Math.random().toString(36).slice(2)
424
+
425
+ var r = await braid_fetch(`/${key}`, {
426
+ method: 'PUT',
427
+ version: ['3'],
428
+ parents: [],
429
+ body: 'xyz'
430
+ })
431
+ if (!r.ok) throw 'got: ' + r.statusCode
432
+
433
+ var r = await braid_fetch(`/${key}`, {
434
+ method: 'PUT',
435
+ version: ['4'],
436
+ parents: [],
437
+ body: 'XYZ'
438
+ })
439
+ if (!r.ok) throw 'got: ' + r.statusCode
440
+
441
+ return await (await braid_fetch(`/${key}`)).text()
442
+ },
443
+ 'XYZ'
444
+ )
445
+
446
+ runTest(
447
+ "test PUTing when server has newer version",
448
+ async () => {
449
+ var key = 'test-' + Math.random().toString(36).slice(2)
450
+
451
+ var r = await braid_fetch(`/${key}`, {
452
+ method: 'PUT',
453
+ version: ['3'],
454
+ parents: [],
455
+ body: 'xyz'
456
+ })
457
+ if (!r.ok) throw 'got: ' + r.statusCode
458
+
459
+ var r = await braid_fetch(`/${key}`, {
460
+ method: 'PUT',
461
+ version: ['2'],
462
+ parents: [],
463
+ body: 'XYZ'
464
+ })
465
+ if (!r.ok) throw 'got: ' + r.statusCode
466
+
467
+ return r.headers.get('version')
468
+ },
469
+ '"3"'
470
+ )
471
+
472
+ runTest(
473
+ "test that version we get back is the version we set",
474
+ async () => {
475
+ var key = 'test-' + Math.random().toString(36).slice(2)
476
+
477
+ var r = await braid_fetch(`/${key}`, {
478
+ method: 'PUT',
479
+ version: ['1760077018883'],
480
+ parents: [],
481
+ body: 'xyz'
482
+ })
483
+ if (!r.ok) throw 'got: ' + r.statusCode
484
+
485
+ var r = await braid_fetch(`/${key}`)
486
+ return r.headers.get('version')
487
+ },
488
+ '"1760077018883"'
489
+ )
490
+
491
+ runTest(
492
+ "test that subscribe gets back editable:true.",
493
+ async () => {
494
+ var key = 'test-' + Math.random().toString(36).slice(2)
495
+
496
+ var r = await braid_fetch(`/${key}`, {
497
+ method: 'PUT',
498
+ body: 'xyz'
499
+ })
500
+ if (!r.ok) throw 'got: ' + r.statusCode
501
+
502
+ var a = new AbortController()
503
+ var r = await braid_fetch(`/${key}`, {
504
+ signal: a.signal,
505
+ subscribe: true,
506
+ parents: ['3']
507
+ })
508
+
509
+ var ret = r.headers.get('editable')
510
+ a.abort()
511
+ return '' + ret
512
+ },
513
+ 'true'
514
+ )
515
+
516
+ runTest(
517
+ "test that we can override editable on the server.",
518
+ async () => {
519
+ var r1 = await braid_fetch(`/eval`, {
520
+ method: 'POST',
521
+ subscribe: true,
522
+ body: `void (async () => {
523
+ req.method = 'GET'
524
+ res.setHeader('editable', 'false')
525
+ braid_blob.serve(req, res, {key: ':test'})
526
+ })()`
527
+ })
528
+ return r1.headers.get('editable')
529
+
530
+ },
531
+ 'false'
532
+ )
533
+
534
+ </script>