braid-blob 0.0.63 → 0.0.65
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/README.md +16 -16
- package/client.js +8 -8
- package/img-live-demo.html +1 -1
- package/img-live.js +50 -11
- package/index.js +116 -116
- package/package.json +1 -1
- package/test/tests.js +1 -1
package/README.md
CHANGED
|
@@ -96,7 +96,7 @@ Subscribe: true
|
|
|
96
96
|
Response (keeps connection open, streams updates):
|
|
97
97
|
|
|
98
98
|
```http
|
|
99
|
-
HTTP/1.1 209
|
|
99
|
+
HTTP/1.1 209 Multiresponse
|
|
100
100
|
Subscribe: true
|
|
101
101
|
Current-Version: "alice-1"
|
|
102
102
|
|
|
@@ -136,10 +136,10 @@ Response:
|
|
|
136
136
|
|
|
137
137
|
```http
|
|
138
138
|
HTTP/1.1 200 OK
|
|
139
|
-
Version: "carol-3"
|
|
139
|
+
Current-Version: "carol-3"
|
|
140
140
|
```
|
|
141
141
|
|
|
142
|
-
If the sent version is older or eclipsed by the server's current version, the returned `Version` will be the server's version (not the one you sent).
|
|
142
|
+
If the sent version is older or eclipsed by the server's current version, the returned `Current-Version` will be the server's version (not the one you sent).
|
|
143
143
|
|
|
144
144
|
The `braid_blob.serve()` method (below) will accept every PUT sent to it, but you can implement access control for any request before passing it to `serve()`, and return e.g. `401 Unauthorized` if you do no want to allow the PUT.
|
|
145
145
|
|
|
@@ -183,7 +183,7 @@ braid_blob.db_folder = './braid-blobs' // Default: ./braid-blobs
|
|
|
183
183
|
Your app becomes a blob server with:
|
|
184
184
|
|
|
185
185
|
```javascript
|
|
186
|
-
braid_blob.serve(req, res,
|
|
186
|
+
braid_blob.serve(req, res, params)
|
|
187
187
|
```
|
|
188
188
|
|
|
189
189
|
This will synchronize the client issuing the given request and response with its blob on disk.
|
|
@@ -191,7 +191,7 @@ This will synchronize the client issuing the given request and response with its
|
|
|
191
191
|
Parameters:
|
|
192
192
|
- `req` - HTTP request object
|
|
193
193
|
- `res` - HTTP response object
|
|
194
|
-
- `
|
|
194
|
+
- `params` - Optional configuration object
|
|
195
195
|
- `key` - The blob on disk to sync with (default: `req.url`)
|
|
196
196
|
|
|
197
197
|
### Sync a remotely served blob to disk
|
|
@@ -199,7 +199,7 @@ Parameters:
|
|
|
199
199
|
Your app becomes a blob client with:
|
|
200
200
|
|
|
201
201
|
```javascript
|
|
202
|
-
braid_blob.sync(key, url,
|
|
202
|
+
braid_blob.sync(key, url, params)
|
|
203
203
|
```
|
|
204
204
|
|
|
205
205
|
Synchronizes a remote URL to its blob on disk.
|
|
@@ -207,7 +207,7 @@ Synchronizes a remote URL to its blob on disk.
|
|
|
207
207
|
Parameters:
|
|
208
208
|
- `key` - The blob on disk (string)
|
|
209
209
|
- `url` - Remote URL (URL object)
|
|
210
|
-
- `
|
|
210
|
+
- `params` - Optional configuration object
|
|
211
211
|
- `signal` - AbortSignal for cancellation (use to stop sync)
|
|
212
212
|
- `content_type` - Content type for requests
|
|
213
213
|
|
|
@@ -216,7 +216,7 @@ Parameters:
|
|
|
216
216
|
#### Read a local or remote blob
|
|
217
217
|
|
|
218
218
|
```javascript
|
|
219
|
-
braid_blob.get(key,
|
|
219
|
+
braid_blob.get(key, params)
|
|
220
220
|
```
|
|
221
221
|
|
|
222
222
|
Retrieves a blob from local storage or a remote URL.
|
|
@@ -238,7 +238,7 @@ braid_blob.get(
|
|
|
238
238
|
|
|
239
239
|
Parameters:
|
|
240
240
|
- `key` - The local blob (if string) or remote URL (if [URL object](https://nodejs.org/api/url.html#class-url)) to read from
|
|
241
|
-
- `
|
|
241
|
+
- `params` - Optional configuration object
|
|
242
242
|
- `version` - Version ID to check existence (use with `head: true`)
|
|
243
243
|
- `parent` - Version ID; when subscribing, only receive updates newer than this
|
|
244
244
|
- `subscribe` - Callback function for real-time updates
|
|
@@ -251,7 +251,7 @@ Returns: `{version, body, content_type}` object, or `null` if not found.
|
|
|
251
251
|
#### Write a local or remote blob
|
|
252
252
|
|
|
253
253
|
```javascript
|
|
254
|
-
braid_blob.put(key, body,
|
|
254
|
+
braid_blob.put(key, body, params)
|
|
255
255
|
```
|
|
256
256
|
|
|
257
257
|
Writes a blob to local storage or a remote URL. Any other peers synchronizing with this blob (via `.serve()`, `.sync()`, or `.get(.., {subscribe: ..}`) will be updated.
|
|
@@ -259,7 +259,7 @@ Writes a blob to local storage or a remote URL. Any other peers synchronizing w
|
|
|
259
259
|
Parameters:
|
|
260
260
|
- `key` - The local blob (if string) or remote URL (if [URL object](https://nodejs.org/api/url.html#class-url)) to write to
|
|
261
261
|
- `body` - Buffer or data to store
|
|
262
|
-
- `
|
|
262
|
+
- `params` - Optional configuration object
|
|
263
263
|
- `version` - Version identifier
|
|
264
264
|
- `content_type` - Content type of the blob
|
|
265
265
|
- `signal` - AbortSignal for cancellation
|
|
@@ -267,14 +267,14 @@ Parameters:
|
|
|
267
267
|
#### Delete a local or remote blob
|
|
268
268
|
|
|
269
269
|
```javascript
|
|
270
|
-
braid_blob.delete(key,
|
|
270
|
+
braid_blob.delete(key, params)
|
|
271
271
|
```
|
|
272
272
|
|
|
273
273
|
Deletes a blob from local storage or a remote URL.
|
|
274
274
|
|
|
275
275
|
Parameters:
|
|
276
276
|
- `key` - The local blob (if string) or remote URL (if [URL object](https://nodejs.org/api/url.html#class-url)) to delete
|
|
277
|
-
- `
|
|
277
|
+
- `params` - Optional configuration object
|
|
278
278
|
- `signal` - AbortSignal for cancellation
|
|
279
279
|
|
|
280
280
|
## Browser Client API
|
|
@@ -300,14 +300,14 @@ A simple browser client is included for subscribing to blob updates.
|
|
|
300
300
|
### Subscribe to remote blob
|
|
301
301
|
|
|
302
302
|
```javascript
|
|
303
|
-
braid_blob_client(url,
|
|
303
|
+
braid_blob_client(url, params)
|
|
304
304
|
```
|
|
305
305
|
|
|
306
|
-
Subscribes to a blob endpoint, and calls `
|
|
306
|
+
Subscribes to a blob endpoint, and calls `params.on_update()` with each update.
|
|
307
307
|
|
|
308
308
|
Parameters:
|
|
309
309
|
- `url` - The blob endpoint URL
|
|
310
|
-
- `
|
|
310
|
+
- `params` - Configuration object
|
|
311
311
|
- `on_update(blob, content_type, version)` - Callback for updates
|
|
312
312
|
- `on_delete` - Callback when blob is deleted
|
|
313
313
|
- `on_error(e)` - Callback for errors
|
package/client.js
CHANGED
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
// // Update the blob with new data
|
|
21
21
|
// await blob.update(body, 'text/plain')
|
|
22
22
|
//
|
|
23
|
-
function braid_blob_client(url,
|
|
24
|
-
var peer =
|
|
23
|
+
function braid_blob_client(url, params = {}) {
|
|
24
|
+
var peer = params.peer || Math.random().toString(36).slice(2)
|
|
25
25
|
var current_version = null
|
|
26
26
|
|
|
27
27
|
braid_fetch(url, {
|
|
@@ -30,24 +30,24 @@ function braid_blob_client(url, options = {}) {
|
|
|
30
30
|
parents: () => [current_version],
|
|
31
31
|
peer,
|
|
32
32
|
retry: () => true,
|
|
33
|
-
signal:
|
|
33
|
+
signal: params.signal
|
|
34
34
|
}).then(res => {
|
|
35
35
|
res.subscribe(async update => {
|
|
36
36
|
if (update.status == 404) {
|
|
37
37
|
current_version = null
|
|
38
|
-
return
|
|
38
|
+
return params.on_delete?.()
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
// Only update if version is newer
|
|
42
42
|
var version = update.version?.[0]
|
|
43
43
|
if (compare_events(version, current_version) > 0) {
|
|
44
44
|
current_version = version
|
|
45
|
-
|
|
45
|
+
params.on_update?.(update.body,
|
|
46
46
|
update.extra_headers?.['content-type'],
|
|
47
47
|
current_version)
|
|
48
48
|
}
|
|
49
|
-
}, e =>
|
|
50
|
-
}).catch(e =>
|
|
49
|
+
}, e => params.on_error?.(e))
|
|
50
|
+
}).catch(e => params.on_error?.(e))
|
|
51
51
|
|
|
52
52
|
return {
|
|
53
53
|
update: async (body, content_type) => {
|
|
@@ -55,7 +55,7 @@ function braid_blob_client(url, options = {}) {
|
|
|
55
55
|
increment_seq(get_event_seq(current_version)))
|
|
56
56
|
current_version = `${peer}-${seq}`
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
params.on_update?.(body, content_type, current_version)
|
|
59
59
|
|
|
60
60
|
await braid_fetch(url, {
|
|
61
61
|
method: 'PUT',
|
package/img-live-demo.html
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
<h1>Braid-Blob Live Image Demo</h1>
|
|
27
27
|
<p>This image updates in real-time across all connected clients.</p>
|
|
28
28
|
|
|
29
|
-
<img live src="/blob.png" alt="Live updating image">
|
|
29
|
+
<img live droppable src="/blob.png" alt="Live updating image">
|
|
30
30
|
|
|
31
31
|
<script src="https://unpkg.com/braid-http@~1.3/braid-http-client.js"></script>
|
|
32
32
|
<script src="client.js"></script>
|
package/img-live.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
// Braid-Blob Live Images
|
|
2
2
|
// requires client.js
|
|
3
3
|
|
|
4
|
-
var live_images = new Map() // img -> ac
|
|
4
|
+
var live_images = new Map() // img -> { ac, client }
|
|
5
5
|
|
|
6
6
|
function sync(img) {
|
|
7
7
|
var url = img.src
|
|
8
8
|
if (!url) return
|
|
9
|
-
if (live_images.has(img)) return
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
// Unsync first to handle attribute changes (e.g. droppable added/removed)
|
|
11
|
+
unsync(img)
|
|
13
12
|
|
|
14
|
-
|
|
13
|
+
var ac = new AbortController()
|
|
14
|
+
var client = braid_blob_client(url, {
|
|
15
15
|
signal: ac.signal,
|
|
16
16
|
on_update: (body, content_type) => {
|
|
17
17
|
var blob = new Blob([body], { type: content_type || 'image/png' })
|
|
@@ -21,16 +21,55 @@ function sync(img) {
|
|
|
21
21
|
console.error('Live image error for', url, error)
|
|
22
22
|
}
|
|
23
23
|
})
|
|
24
|
+
live_images.set(img, { ac, client })
|
|
25
|
+
|
|
26
|
+
if (img.hasAttribute('droppable'))
|
|
27
|
+
make_droppable(img)
|
|
24
28
|
}
|
|
25
29
|
|
|
26
30
|
function unsync(img) {
|
|
27
|
-
var
|
|
28
|
-
if (
|
|
29
|
-
ac.abort()
|
|
31
|
+
var entry = live_images.get(img)
|
|
32
|
+
if (entry) {
|
|
33
|
+
entry.ac.abort()
|
|
30
34
|
live_images.delete(img)
|
|
31
35
|
}
|
|
32
36
|
}
|
|
33
37
|
|
|
38
|
+
function make_droppable(img) {
|
|
39
|
+
img.addEventListener('dragenter', function(e) {
|
|
40
|
+
img.style.outline = '3px dashed #007bff'
|
|
41
|
+
img.style.outlineOffset = '3px'
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
img.addEventListener('dragleave', function(e) {
|
|
45
|
+
img.style.outline = ''
|
|
46
|
+
img.style.outlineOffset = ''
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
img.addEventListener('dragover', function(e) {
|
|
50
|
+
e.preventDefault()
|
|
51
|
+
e.dataTransfer.dropEffect = 'copy'
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
img.addEventListener('drop', function(e) {
|
|
55
|
+
e.preventDefault()
|
|
56
|
+
img.style.outline = ''
|
|
57
|
+
img.style.outlineOffset = ''
|
|
58
|
+
|
|
59
|
+
var file = e.dataTransfer.files[0]
|
|
60
|
+
if (!file || !file.type.startsWith('image/')) return
|
|
61
|
+
|
|
62
|
+
var entry = live_images.get(img)
|
|
63
|
+
if (!entry) return
|
|
64
|
+
|
|
65
|
+
var reader = new FileReader()
|
|
66
|
+
reader.onload = function() {
|
|
67
|
+
entry.client.update(reader.result, file.type)
|
|
68
|
+
}
|
|
69
|
+
reader.readAsArrayBuffer(file)
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
|
|
34
73
|
var observer = new MutationObserver(function(mutations) {
|
|
35
74
|
mutations.forEach(function(mutation) {
|
|
36
75
|
mutation.addedNodes.forEach(function(node) {
|
|
@@ -47,10 +86,10 @@ var observer = new MutationObserver(function(mutations) {
|
|
|
47
86
|
node.querySelectorAll('img[live]').forEach(unsync)
|
|
48
87
|
}
|
|
49
88
|
})
|
|
50
|
-
if (mutation.type === 'attributes' && mutation.
|
|
89
|
+
if (mutation.type === 'attributes' && mutation.target.tagName === 'IMG') {
|
|
51
90
|
if (mutation.target.hasAttribute('live'))
|
|
52
91
|
sync(mutation.target)
|
|
53
|
-
else
|
|
92
|
+
else if (mutation.attributeName === 'live')
|
|
54
93
|
unsync(mutation.target)
|
|
55
94
|
}
|
|
56
95
|
})
|
|
@@ -66,7 +105,7 @@ function init() {
|
|
|
66
105
|
childList: true,
|
|
67
106
|
subtree: true,
|
|
68
107
|
attributes: true,
|
|
69
|
-
attributeFilter: ['live']
|
|
108
|
+
attributeFilter: ['live', 'droppable']
|
|
70
109
|
})
|
|
71
110
|
|
|
72
111
|
document.querySelectorAll('img[live]').forEach(sync)
|
package/index.js
CHANGED
|
@@ -13,9 +13,9 @@ function create_braid_blob() {
|
|
|
13
13
|
reconnect_delay_ms: 1000,
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
braid_blob.sync = (a, b,
|
|
17
|
-
|
|
18
|
-
if (!
|
|
16
|
+
braid_blob.sync = (a, b, params = {}) => {
|
|
17
|
+
params = normalize_params(params)
|
|
18
|
+
if (!params.peer) params.peer = Math.random().toString(36).slice(2)
|
|
19
19
|
|
|
20
20
|
// Support for same-type params removed for now,
|
|
21
21
|
// since it is unused, unoptimized,
|
|
@@ -28,26 +28,26 @@ function create_braid_blob() {
|
|
|
28
28
|
let swap = a; a = b; b = swap
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
reconnector(
|
|
31
|
+
reconnector(params.signal, (_e, count) => {
|
|
32
32
|
var delay = braid_blob.reconnect_delay_ms ?? Math.min(count, 3) * 1000
|
|
33
33
|
console.log(`disconnected from ${b.href}, retrying in ${delay}ms`)
|
|
34
34
|
return delay
|
|
35
35
|
}, async (signal, handle_error) => {
|
|
36
36
|
if (signal.aborted) return
|
|
37
|
-
if (
|
|
37
|
+
if (params.on_pre_connect) await params.on_pre_connect()
|
|
38
38
|
|
|
39
39
|
try {
|
|
40
40
|
// Check if remote has our current version (simple fork-point check)
|
|
41
41
|
var server_has_our_version = false
|
|
42
42
|
var local_version = (await braid_blob.get(a, {
|
|
43
|
-
...
|
|
43
|
+
...params,
|
|
44
44
|
signal,
|
|
45
45
|
head: true
|
|
46
46
|
}))?.version
|
|
47
47
|
if (signal.aborted) return
|
|
48
48
|
if (local_version) {
|
|
49
49
|
var r = await braid_blob.get(b, {
|
|
50
|
-
...
|
|
50
|
+
...params,
|
|
51
51
|
signal,
|
|
52
52
|
head: true,
|
|
53
53
|
dont_retry: true,
|
|
@@ -59,14 +59,14 @@ function create_braid_blob() {
|
|
|
59
59
|
|
|
60
60
|
// Local -> remote
|
|
61
61
|
await braid_blob.get(a, {
|
|
62
|
-
...
|
|
62
|
+
...params,
|
|
63
63
|
signal,
|
|
64
64
|
parents: server_has_our_version ? local_version : null,
|
|
65
65
|
subscribe: async update => {
|
|
66
66
|
try {
|
|
67
67
|
if (update.delete) {
|
|
68
68
|
var x = await braid_blob.delete(b, {
|
|
69
|
-
...
|
|
69
|
+
...params,
|
|
70
70
|
signal,
|
|
71
71
|
dont_retry: true,
|
|
72
72
|
content_type: update.content_type,
|
|
@@ -75,15 +75,15 @@ function create_braid_blob() {
|
|
|
75
75
|
if (!x.ok) handle_error(new Error('failed to delete'))
|
|
76
76
|
} else {
|
|
77
77
|
var x = await braid_blob.put(b, update.body, {
|
|
78
|
-
...
|
|
78
|
+
...params,
|
|
79
79
|
signal,
|
|
80
80
|
dont_retry: true,
|
|
81
81
|
version: update.version,
|
|
82
82
|
content_type: update.content_type,
|
|
83
83
|
})
|
|
84
84
|
if (signal.aborted) return
|
|
85
|
-
if ((x.status === 401 || x.status === 403) &&
|
|
86
|
-
await
|
|
85
|
+
if ((x.status === 401 || x.status === 403) && params.on_unauthorized) {
|
|
86
|
+
await params.on_unauthorized?.()
|
|
87
87
|
} else if (!x.ok) handle_error(new Error('failed to PUT: ' + x.status))
|
|
88
88
|
}
|
|
89
89
|
} catch (e) { handle_error(e) }
|
|
@@ -92,39 +92,39 @@ function create_braid_blob() {
|
|
|
92
92
|
|
|
93
93
|
// Remote -> local
|
|
94
94
|
var remote_res = await braid_blob.get(b, {
|
|
95
|
-
...
|
|
95
|
+
...params,
|
|
96
96
|
signal,
|
|
97
97
|
dont_retry: true,
|
|
98
98
|
parents: local_version,
|
|
99
99
|
subscribe: async update => {
|
|
100
100
|
if (update.delete) await braid_blob.delete(a, {
|
|
101
|
-
...
|
|
101
|
+
...params,
|
|
102
102
|
signal,
|
|
103
103
|
content_type: update.content_type,
|
|
104
104
|
})
|
|
105
105
|
else await braid_blob.put(a, update.body, {
|
|
106
|
-
...
|
|
106
|
+
...params,
|
|
107
107
|
signal,
|
|
108
108
|
version: update.version,
|
|
109
109
|
content_type: update.content_type,
|
|
110
110
|
})
|
|
111
111
|
},
|
|
112
112
|
on_error: e => {
|
|
113
|
-
|
|
113
|
+
params.on_disconnect?.()
|
|
114
114
|
handle_error(e)
|
|
115
115
|
}
|
|
116
116
|
})
|
|
117
|
-
|
|
117
|
+
params.on_res?.(remote_res)
|
|
118
118
|
} catch (e) { handle_error(e) }
|
|
119
119
|
})
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
-
braid_blob.serve = async (req, res,
|
|
122
|
+
braid_blob.serve = async (req, res, params = {}) => {
|
|
123
123
|
await braid_blob.init()
|
|
124
124
|
|
|
125
|
-
if (!
|
|
125
|
+
if (!params.key) {
|
|
126
126
|
var url = new URL(req.url, 'http://localhost')
|
|
127
|
-
|
|
127
|
+
params.key = url.pathname
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
free_cors(res)
|
|
@@ -144,7 +144,7 @@ function create_braid_blob() {
|
|
|
144
144
|
res.setHeader("Merge-Type", "aww")
|
|
145
145
|
|
|
146
146
|
try {
|
|
147
|
-
var result = await braid_blob.get(
|
|
147
|
+
var result = await braid_blob.get(params.key, {
|
|
148
148
|
peer: req.peer,
|
|
149
149
|
head: req.method === "HEAD",
|
|
150
150
|
version: req.version,
|
|
@@ -198,15 +198,15 @@ function create_braid_blob() {
|
|
|
198
198
|
}
|
|
199
199
|
} else if (req.method === 'PUT') {
|
|
200
200
|
// Handle PUT request to update binary files
|
|
201
|
-
var event = await braid_blob.put(
|
|
201
|
+
var event = await braid_blob.put(params.key, body, {
|
|
202
202
|
version: req.version,
|
|
203
203
|
content_type: req.headers['content-type'],
|
|
204
204
|
peer: req.peer
|
|
205
205
|
})
|
|
206
|
-
res.setHeader("Version", version_to_header(event != null ? [event] : []))
|
|
206
|
+
res.setHeader("Current-Version", version_to_header(event != null ? [event] : []))
|
|
207
207
|
res.end('')
|
|
208
208
|
} else if (req.method === 'DELETE') {
|
|
209
|
-
await braid_blob.delete(
|
|
209
|
+
await braid_blob.delete(params.key, {
|
|
210
210
|
content_type: req.headers['content-type'],
|
|
211
211
|
peer: req.peer
|
|
212
212
|
})
|
|
@@ -214,44 +214,44 @@ function create_braid_blob() {
|
|
|
214
214
|
}
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
-
braid_blob.get = async (key,
|
|
218
|
-
|
|
217
|
+
braid_blob.get = async (key, params = {}) => {
|
|
218
|
+
params = normalize_params(params)
|
|
219
219
|
|
|
220
220
|
// Handle URL case - make a remote GET request
|
|
221
221
|
if (key instanceof URL) {
|
|
222
|
-
var
|
|
223
|
-
signal:
|
|
224
|
-
subscribe:
|
|
222
|
+
var fetch_params = {
|
|
223
|
+
signal: params.signal,
|
|
224
|
+
subscribe: params.subscribe,
|
|
225
225
|
heartbeats: 120,
|
|
226
226
|
}
|
|
227
|
-
if (!
|
|
228
|
-
|
|
227
|
+
if (!params.dont_retry) {
|
|
228
|
+
fetch_params.retry = (res) => res.status !== 309 &&
|
|
229
229
|
res.status !== 404 && res.status !== 406
|
|
230
230
|
}
|
|
231
|
-
if (
|
|
231
|
+
if (params.head) fetch_params.method = 'HEAD'
|
|
232
232
|
for (var x of ['headers', 'parents', 'version', 'peer'])
|
|
233
|
-
if (
|
|
234
|
-
if (
|
|
235
|
-
|
|
236
|
-
'Accept':
|
|
233
|
+
if (params[x] != null) fetch_params[x] = params[x]
|
|
234
|
+
if (params.content_type)
|
|
235
|
+
fetch_params.headers = { ...fetch_params.headers,
|
|
236
|
+
'Accept': params.content_type }
|
|
237
237
|
|
|
238
|
-
var res = await braid_fetch(key.href,
|
|
238
|
+
var res = await braid_fetch(key.href, fetch_params)
|
|
239
239
|
|
|
240
240
|
if (!res.ok)
|
|
241
|
-
if (
|
|
241
|
+
if (params.subscribe) throw new Error('failed to subscribe')
|
|
242
242
|
else return null
|
|
243
243
|
|
|
244
244
|
var result = {}
|
|
245
245
|
if (res.version) result.version = res.version
|
|
246
246
|
|
|
247
|
-
if (
|
|
247
|
+
if (params.head) return result
|
|
248
248
|
|
|
249
|
-
if (
|
|
249
|
+
if (params.subscribe) {
|
|
250
250
|
res.subscribe(async update => {
|
|
251
251
|
if (update.status === 404) update.delete = true
|
|
252
252
|
update.content_type = update.extra_headers['content-type']
|
|
253
|
-
await
|
|
254
|
-
}, e =>
|
|
253
|
+
await params.subscribe(update)
|
|
254
|
+
}, e => params.on_error?.(e))
|
|
255
255
|
return res
|
|
256
256
|
} else {
|
|
257
257
|
result.body = await res.arrayBuffer()
|
|
@@ -260,103 +260,103 @@ function create_braid_blob() {
|
|
|
260
260
|
}
|
|
261
261
|
|
|
262
262
|
await braid_blob.init()
|
|
263
|
-
if (
|
|
263
|
+
if (params.signal?.aborted) return
|
|
264
264
|
|
|
265
265
|
return await within_fiber(key, async () => {
|
|
266
266
|
var meta = await get_meta(key)
|
|
267
|
-
if (
|
|
267
|
+
if (params.signal?.aborted) return
|
|
268
268
|
|
|
269
|
-
if (!meta.event && !
|
|
269
|
+
if (!meta.event && !params.subscribe) return null
|
|
270
270
|
|
|
271
271
|
var result = {
|
|
272
272
|
version: meta.event ? [meta.event] : [],
|
|
273
273
|
content_type: meta.content_type
|
|
274
274
|
}
|
|
275
275
|
|
|
276
|
-
if (
|
|
277
|
-
if (
|
|
276
|
+
if (params.header_cb) await params.header_cb(result)
|
|
277
|
+
if (params.signal?.aborted) return
|
|
278
278
|
|
|
279
279
|
// Check if requested version/parents is newer than what we have - if so, we don't have it
|
|
280
|
-
if (!
|
|
281
|
-
if (compare_events(
|
|
282
|
-
throw new Error('unknown version: ' +
|
|
283
|
-
if (compare_events(
|
|
284
|
-
throw new Error('unknown version: ' +
|
|
280
|
+
if (!params.subscribe) {
|
|
281
|
+
if (compare_events(params.version?.[0], meta.event) > 0)
|
|
282
|
+
throw new Error('unknown version: ' + params.version)
|
|
283
|
+
if (compare_events(params.parents?.[0], meta.event) > 0)
|
|
284
|
+
throw new Error('unknown version: ' + params.parents)
|
|
285
285
|
}
|
|
286
|
-
if (
|
|
286
|
+
if (params.head) return result
|
|
287
287
|
|
|
288
|
-
if (
|
|
288
|
+
if (params.subscribe) {
|
|
289
289
|
var subscribe_chain = Promise.resolve()
|
|
290
|
-
|
|
290
|
+
params.my_subscribe = (x) => subscribe_chain =
|
|
291
291
|
subscribe_chain.then(() =>
|
|
292
|
-
!
|
|
292
|
+
!params.signal?.aborted && params.subscribe(x))
|
|
293
293
|
|
|
294
294
|
// Start a subscription for future updates
|
|
295
295
|
if (!braid_blob.key_to_subs[key])
|
|
296
296
|
braid_blob.key_to_subs[key] = new Map()
|
|
297
297
|
|
|
298
|
-
var peer =
|
|
298
|
+
var peer = params.peer || Math.random().toString(36).slice(2)
|
|
299
299
|
braid_blob.key_to_subs[key].set(peer, {
|
|
300
300
|
sendUpdate: (update) => {
|
|
301
|
-
if (update.delete)
|
|
302
|
-
else if (compare_events(update.version[0],
|
|
303
|
-
|
|
301
|
+
if (update.delete) params.my_subscribe(update)
|
|
302
|
+
else if (compare_events(update.version[0], params.parents?.[0]) > 0)
|
|
303
|
+
params.my_subscribe(update)
|
|
304
304
|
}
|
|
305
305
|
})
|
|
306
306
|
|
|
307
|
-
|
|
307
|
+
params.signal?.addEventListener('abort', () => {
|
|
308
308
|
braid_blob.key_to_subs[key].delete(peer)
|
|
309
309
|
if (!braid_blob.key_to_subs[key].size)
|
|
310
310
|
delete braid_blob.key_to_subs[key]
|
|
311
311
|
})
|
|
312
312
|
|
|
313
|
-
if (
|
|
314
|
-
if (
|
|
313
|
+
if (params.before_send_cb) await params.before_send_cb()
|
|
314
|
+
if (params.signal?.aborted) return
|
|
315
315
|
|
|
316
316
|
// Send an immediate update if needed
|
|
317
|
-
if (compare_events(result.version?.[0],
|
|
317
|
+
if (compare_events(result.version?.[0], params.parents?.[0]) > 0) {
|
|
318
318
|
result.sent = true
|
|
319
|
-
result.body = await (
|
|
320
|
-
|
|
319
|
+
result.body = await (params.db || braid_blob.db).read(key)
|
|
320
|
+
params.my_subscribe(result)
|
|
321
321
|
}
|
|
322
322
|
} else {
|
|
323
323
|
// If not subscribe, send the body now
|
|
324
|
-
result.body = await (
|
|
324
|
+
result.body = await (params.db || braid_blob.db).read(key)
|
|
325
325
|
}
|
|
326
326
|
|
|
327
327
|
return result
|
|
328
328
|
})
|
|
329
329
|
}
|
|
330
330
|
|
|
331
|
-
braid_blob.put = async (key, body,
|
|
332
|
-
|
|
331
|
+
braid_blob.put = async (key, body, params = {}) => {
|
|
332
|
+
params = normalize_params(params)
|
|
333
333
|
|
|
334
334
|
// Handle URL case - make a remote PUT request
|
|
335
335
|
if (key instanceof URL) {
|
|
336
|
-
var
|
|
336
|
+
var fetch_params = {
|
|
337
337
|
method: 'PUT',
|
|
338
|
-
signal:
|
|
338
|
+
signal: params.signal,
|
|
339
339
|
body
|
|
340
340
|
}
|
|
341
|
-
if (!
|
|
342
|
-
|
|
341
|
+
if (!params.dont_retry)
|
|
342
|
+
fetch_params.retry = () => true
|
|
343
343
|
for (var x of ['headers', 'version', 'peer'])
|
|
344
|
-
if (
|
|
345
|
-
if (
|
|
346
|
-
|
|
347
|
-
'Content-Type':
|
|
344
|
+
if (params[x] != null) fetch_params[x] = params[x]
|
|
345
|
+
if (params.content_type)
|
|
346
|
+
fetch_params.headers = { ...fetch_params.headers,
|
|
347
|
+
'Content-Type': params.content_type }
|
|
348
348
|
|
|
349
|
-
return await braid_fetch(key.href,
|
|
349
|
+
return await braid_fetch(key.href, fetch_params)
|
|
350
350
|
}
|
|
351
351
|
|
|
352
352
|
await braid_blob.init()
|
|
353
|
-
if (
|
|
353
|
+
if (params.signal?.aborted) return
|
|
354
354
|
|
|
355
355
|
return await within_fiber(key, async () => {
|
|
356
356
|
var meta = await get_meta(key)
|
|
357
|
-
if (
|
|
357
|
+
if (params.signal?.aborted) return
|
|
358
358
|
|
|
359
|
-
var their_e =
|
|
359
|
+
var their_e = params.version ? params.version[0] :
|
|
360
360
|
// we'll give them a event id in this case
|
|
361
361
|
`${braid_blob.peer}-${max_seq('' + Date.now(),
|
|
362
362
|
meta.event ? increment_seq(get_event_seq(meta.event)) : '')}`
|
|
@@ -364,15 +364,15 @@ function create_braid_blob() {
|
|
|
364
364
|
if (compare_events(their_e, meta.event) > 0) {
|
|
365
365
|
meta.event = their_e
|
|
366
366
|
|
|
367
|
-
if (!
|
|
368
|
-
await (
|
|
369
|
-
if (
|
|
367
|
+
if (!params.skip_write)
|
|
368
|
+
await (params.db || braid_blob.db).write(key, body)
|
|
369
|
+
if (params.signal?.aborted) return
|
|
370
370
|
|
|
371
|
-
if (
|
|
372
|
-
meta.content_type =
|
|
371
|
+
if (params.content_type)
|
|
372
|
+
meta.content_type = params.content_type
|
|
373
373
|
|
|
374
374
|
save_meta(key, meta)
|
|
375
|
-
if (
|
|
375
|
+
if (params.signal?.aborted) return
|
|
376
376
|
|
|
377
377
|
// Notify all subscriptions of the update
|
|
378
378
|
// (except the peer which made the PUT request itself)
|
|
@@ -383,7 +383,7 @@ function create_braid_blob() {
|
|
|
383
383
|
}
|
|
384
384
|
if (braid_blob.key_to_subs[key])
|
|
385
385
|
for (var [peer, sub] of braid_blob.key_to_subs[key].entries())
|
|
386
|
-
if (!
|
|
386
|
+
if (!params.peer || params.peer !== peer)
|
|
387
387
|
await sub.sendUpdate(update)
|
|
388
388
|
}
|
|
389
389
|
|
|
@@ -391,35 +391,35 @@ function create_braid_blob() {
|
|
|
391
391
|
})
|
|
392
392
|
}
|
|
393
393
|
|
|
394
|
-
braid_blob.delete = async (key,
|
|
395
|
-
|
|
394
|
+
braid_blob.delete = async (key, params = {}) => {
|
|
395
|
+
params = normalize_params(params)
|
|
396
396
|
|
|
397
397
|
// Handle URL case - make a remote DELETE request
|
|
398
398
|
if (key instanceof URL) {
|
|
399
|
-
var
|
|
399
|
+
var fetch_params = {
|
|
400
400
|
method: 'DELETE',
|
|
401
|
-
signal:
|
|
401
|
+
signal: params.signal
|
|
402
402
|
}
|
|
403
|
-
if (!
|
|
404
|
-
|
|
403
|
+
if (!params.dont_retry)
|
|
404
|
+
fetch_params.retry = (res) => res.status !== 309 &&
|
|
405
405
|
res.status !== 404 && res.status !== 406
|
|
406
406
|
for (var x of ['headers', 'peer'])
|
|
407
|
-
if (
|
|
408
|
-
if (
|
|
409
|
-
|
|
410
|
-
'Accept':
|
|
407
|
+
if (params[x] != null) fetch_params[x] = params[x]
|
|
408
|
+
if (params.content_type)
|
|
409
|
+
fetch_params.headers = { ...fetch_params.headers,
|
|
410
|
+
'Accept': params.content_type }
|
|
411
411
|
|
|
412
|
-
return await braid_fetch(key.href,
|
|
412
|
+
return await braid_fetch(key.href, fetch_params)
|
|
413
413
|
}
|
|
414
414
|
|
|
415
415
|
await braid_blob.init()
|
|
416
|
-
if (
|
|
416
|
+
if (params.signal?.aborted) return
|
|
417
417
|
|
|
418
418
|
return await within_fiber(key, async () => {
|
|
419
419
|
var meta = await get_meta(key)
|
|
420
|
-
if (
|
|
420
|
+
if (params.signal?.aborted) return
|
|
421
421
|
|
|
422
|
-
await (
|
|
422
|
+
await (params.db || braid_blob.db).delete(key)
|
|
423
423
|
await delete_meta(key)
|
|
424
424
|
|
|
425
425
|
// Notify all subscriptions of the delete
|
|
@@ -430,7 +430,7 @@ function create_braid_blob() {
|
|
|
430
430
|
}
|
|
431
431
|
if (braid_blob.key_to_subs[key])
|
|
432
432
|
for (var [peer, sub] of braid_blob.key_to_subs[key].entries())
|
|
433
|
-
if (!
|
|
433
|
+
if (!params.peer || params.peer !== peer)
|
|
434
434
|
sub.sendUpdate(update)
|
|
435
435
|
})
|
|
436
436
|
}
|
|
@@ -728,9 +728,9 @@ function create_braid_blob() {
|
|
|
728
728
|
return s
|
|
729
729
|
}
|
|
730
730
|
|
|
731
|
-
function
|
|
732
|
-
if (!
|
|
733
|
-
|
|
731
|
+
function normalize_params(params = {}) {
|
|
732
|
+
if (!normalize_params.special) {
|
|
733
|
+
normalize_params.special = {
|
|
734
734
|
version: 'version',
|
|
735
735
|
parents: 'parents',
|
|
736
736
|
'content-type': 'content_type',
|
|
@@ -740,28 +740,28 @@ function create_braid_blob() {
|
|
|
740
740
|
}
|
|
741
741
|
|
|
742
742
|
var normalized = {}
|
|
743
|
-
Object.assign(normalized,
|
|
743
|
+
Object.assign(normalized, params)
|
|
744
744
|
|
|
745
745
|
// Normalize top-level accept to content_type
|
|
746
|
-
if (
|
|
747
|
-
normalized.content_type =
|
|
746
|
+
if (params.accept) {
|
|
747
|
+
normalized.content_type = params.accept
|
|
748
748
|
delete normalized.accept
|
|
749
749
|
}
|
|
750
750
|
|
|
751
|
-
if (
|
|
751
|
+
if (params.headers) {
|
|
752
752
|
normalized.headers = {}
|
|
753
|
-
for (var [k, v] of (
|
|
754
|
-
|
|
755
|
-
Object.entries(
|
|
756
|
-
var s =
|
|
753
|
+
for (var [k, v] of (params.headers instanceof Headers ?
|
|
754
|
+
params.headers.entries() :
|
|
755
|
+
Object.entries(params.headers))) {
|
|
756
|
+
var s = normalize_params.special[k.toLowerCase()]
|
|
757
757
|
if (s) normalized[s] = v
|
|
758
758
|
else normalized.headers[k] = v
|
|
759
759
|
}
|
|
760
760
|
}
|
|
761
761
|
|
|
762
762
|
// Normalize parent -> parents
|
|
763
|
-
if (
|
|
764
|
-
normalized.parents =
|
|
763
|
+
if (params.parent)
|
|
764
|
+
normalized.parents = params.parent
|
|
765
765
|
|
|
766
766
|
// Normalize version/parents: allow strings, wrap in array for internal use
|
|
767
767
|
if (typeof normalized.version === 'string')
|
package/package.json
CHANGED