hypercore-fetch 9.6.0 → 9.7.0
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 +10 -0
- package/index.js +60 -13
- package/package.json +2 -2
- package/test.js +25 -15
package/README.md
CHANGED
|
@@ -142,6 +142,16 @@ Note that you must use the name `file` for uploaded files.
|
|
|
142
142
|
|
|
143
143
|
`NAME` can either be the 52 character [z32 encoded](https://github.com/mafintosh/z32) key for a Hyperdrive or Hypercore , or a domain to parse with the [DNSLink](https://www.dnslink.io/) standard.
|
|
144
144
|
|
|
145
|
+
### `fetch('hyper://NAME/', {method: 'DELETE'})`
|
|
146
|
+
|
|
147
|
+
You can purge all the stored data for a hyperdrive by sending a `DELETE` to it's root.
|
|
148
|
+
|
|
149
|
+
If this is a writable drive, your data will get fully clearned and trying to write to it again will lead to data corruption.
|
|
150
|
+
|
|
151
|
+
If you try to load this drive again data will be loaded from scratch.
|
|
152
|
+
|
|
153
|
+
`NAME` can either be the 52 character [z32 encoded](https://github.com/mafintosh/z32) key for a Hyperdrive or Hypercore , or a domain to parse with the [DNSLink](https://www.dnslink.io/) standard.
|
|
154
|
+
|
|
145
155
|
### `fetch('hyper://NAME/example.txt', {method: 'DELETE'})`
|
|
146
156
|
|
|
147
157
|
You can delete a file or directory tree in a Hyperdrive by using the `DELETE` method.
|
package/index.js
CHANGED
|
@@ -36,13 +36,16 @@ const BASIC_METHODS = [
|
|
|
36
36
|
]
|
|
37
37
|
|
|
38
38
|
export const ERROR_KEY_NOT_CREATED = 'Must create key with POST before reading'
|
|
39
|
+
export const ERROR_DRIVE_EMPTY = 'Could not find data in drive, make sure your key is correct and that there are peers online to load data from'
|
|
39
40
|
|
|
40
41
|
const INDEX_FILES = [
|
|
41
42
|
'index.html',
|
|
42
43
|
'index.md',
|
|
43
44
|
'index.gmi',
|
|
44
45
|
'index.gemini',
|
|
45
|
-
'
|
|
46
|
+
'index.org',
|
|
47
|
+
'README.md',
|
|
48
|
+
'README.org'
|
|
46
49
|
]
|
|
47
50
|
|
|
48
51
|
async function DEFAULT_RENDER_INDEX (url, files, fetch) {
|
|
@@ -70,7 +73,9 @@ export default async function makeHyperFetch ({
|
|
|
70
73
|
timeout = DEFAULT_TIMEOUT,
|
|
71
74
|
renderIndex = DEFAULT_RENDER_INDEX
|
|
72
75
|
}) {
|
|
73
|
-
const { fetch, router } = makeRoutedFetch(
|
|
76
|
+
const { fetch, router } = makeRoutedFetch({
|
|
77
|
+
onError
|
|
78
|
+
})
|
|
74
79
|
|
|
75
80
|
// Map loaded drive hostnames to their keys
|
|
76
81
|
// TODO: Track LRU + cache clearing
|
|
@@ -91,6 +96,7 @@ export default async function makeHyperFetch ({
|
|
|
91
96
|
|
|
92
97
|
router.put(`hyper://*/${SPECIAL_FOLDER}/${VERSION_FOLDER_NAME}/**`, putFilesVersioned)
|
|
93
98
|
router.put('hyper://*/**', putFiles)
|
|
99
|
+
router.delete('hyper://*/', deleteDrive)
|
|
94
100
|
router.delete(`hyper://*/${SPECIAL_FOLDER}/${VERSION_FOLDER_NAME}/**`, deleteFilesVersioned)
|
|
95
101
|
router.delete('hyper://*/**', deleteFiles)
|
|
96
102
|
}
|
|
@@ -100,6 +106,16 @@ export default async function makeHyperFetch ({
|
|
|
100
106
|
router.get('hyper://*/**', getFiles)
|
|
101
107
|
router.head('hyper://*/**', headFiles)
|
|
102
108
|
|
|
109
|
+
async function onError (e, request) {
|
|
110
|
+
return {
|
|
111
|
+
status: e.statusCode || 500,
|
|
112
|
+
headers: {
|
|
113
|
+
'Content-Type': 'text/plain; charset=utf-8'
|
|
114
|
+
},
|
|
115
|
+
body: e.stack
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
103
119
|
async function getCore (hostname) {
|
|
104
120
|
if (cores.has(hostname)) {
|
|
105
121
|
return cores.get(hostname)
|
|
@@ -128,18 +144,30 @@ export default async function makeHyperFetch ({
|
|
|
128
144
|
return dbCore
|
|
129
145
|
}
|
|
130
146
|
|
|
131
|
-
async function getDrive (hostname) {
|
|
147
|
+
async function getDrive (hostname, errorOnNew = false) {
|
|
132
148
|
if (drives.has(hostname)) {
|
|
133
149
|
return drives.get(hostname)
|
|
134
150
|
}
|
|
135
151
|
|
|
136
152
|
const core = await getCore(hostname)
|
|
137
153
|
|
|
154
|
+
if (!core.length && errorOnNew) {
|
|
155
|
+
await core.close()
|
|
156
|
+
const e = new Error(ERROR_DRIVE_EMPTY)
|
|
157
|
+
e.statusCode = 404
|
|
158
|
+
throw e
|
|
159
|
+
}
|
|
160
|
+
|
|
138
161
|
const corestore = sdk.namespace(core.id)
|
|
139
162
|
const drive = new Hyperdrive(corestore, core.key)
|
|
140
163
|
|
|
141
164
|
await drive.ready()
|
|
142
165
|
|
|
166
|
+
drive.once('close', () => {
|
|
167
|
+
drives.delete(drive.core.id)
|
|
168
|
+
drives.delete(hostname)
|
|
169
|
+
})
|
|
170
|
+
|
|
143
171
|
drives.set(drive.core.id, drive)
|
|
144
172
|
drives.set(hostname, drive)
|
|
145
173
|
|
|
@@ -151,8 +179,11 @@ export default async function makeHyperFetch ({
|
|
|
151
179
|
return drives.get(key)
|
|
152
180
|
}
|
|
153
181
|
const core = await getDBCoreForName(key)
|
|
182
|
+
|
|
154
183
|
if (!core.length && errorOnNew) {
|
|
155
|
-
|
|
184
|
+
const e = new Error(ERROR_KEY_NOT_CREATED)
|
|
185
|
+
e.statusCode = 404
|
|
186
|
+
throw e
|
|
156
187
|
}
|
|
157
188
|
|
|
158
189
|
const corestore = sdk.namespace(key)
|
|
@@ -160,7 +191,14 @@ export default async function makeHyperFetch ({
|
|
|
160
191
|
|
|
161
192
|
await drive.ready()
|
|
162
193
|
|
|
194
|
+
drive.once('close', () => {
|
|
195
|
+
drives.delete(key)
|
|
196
|
+
drives.delete(drive.url)
|
|
197
|
+
drives.delete(drive.core.id)
|
|
198
|
+
})
|
|
199
|
+
|
|
163
200
|
drives.set(key, drive)
|
|
201
|
+
drives.set(drive.url, drive)
|
|
164
202
|
drives.set(drive.core.id, drive)
|
|
165
203
|
|
|
166
204
|
return drive
|
|
@@ -361,7 +399,7 @@ export default async function makeHyperFetch ({
|
|
|
361
399
|
const contentType = request.headers.get('Content-Type') || ''
|
|
362
400
|
const isFormData = contentType.includes('multipart/form-data')
|
|
363
401
|
|
|
364
|
-
const drive = await getDrive(`hyper://${hostname}
|
|
402
|
+
const drive = await getDrive(`hyper://${hostname}/`, true)
|
|
365
403
|
|
|
366
404
|
if (!drive.db.feed.writable) {
|
|
367
405
|
return { status: 403, body: `Cannot PUT file to read-only drive: ${drive.url}`, headers: { Location: request.url } }
|
|
@@ -419,11 +457,20 @@ export default async function makeHyperFetch ({
|
|
|
419
457
|
}
|
|
420
458
|
}
|
|
421
459
|
|
|
460
|
+
async function deleteDrive (request) {
|
|
461
|
+
const { hostname } = new URL(request.url)
|
|
462
|
+
const drive = await getDrive(`hyper://${hostname}/`, true)
|
|
463
|
+
|
|
464
|
+
await drive.purge()
|
|
465
|
+
|
|
466
|
+
return { status: 200 }
|
|
467
|
+
}
|
|
468
|
+
|
|
422
469
|
async function deleteFiles (request) {
|
|
423
470
|
const { hostname, pathname: rawPathname } = new URL(request.url)
|
|
424
471
|
const pathname = decodeURI(ensureLeadingSlash(rawPathname))
|
|
425
472
|
|
|
426
|
-
const drive = await getDrive(`hyper://${hostname}
|
|
473
|
+
const drive = await getDrive(`hyper://${hostname}/`, true)
|
|
427
474
|
|
|
428
475
|
if (!drive.db.feed.writable) {
|
|
429
476
|
return { status: 403, body: `Cannot DELETE file in read-only drive: ${drive.url}`, headers: { Location: request.url } }
|
|
@@ -475,7 +522,7 @@ export default async function makeHyperFetch ({
|
|
|
475
522
|
const version = parts[3]
|
|
476
523
|
const realPath = ensureLeadingSlash(parts.slice(4).join('/'))
|
|
477
524
|
|
|
478
|
-
const drive = await getDrive(`hyper://${hostname}
|
|
525
|
+
const drive = await getDrive(`hyper://${hostname}/`, true)
|
|
479
526
|
|
|
480
527
|
const snapshot = await drive.checkout(version)
|
|
481
528
|
|
|
@@ -491,7 +538,7 @@ export default async function makeHyperFetch ({
|
|
|
491
538
|
const isRanged = request.headers.get('Range') || ''
|
|
492
539
|
const noResolve = searchParams.has('noResolve')
|
|
493
540
|
|
|
494
|
-
const drive = await getDrive(`hyper://${hostname}
|
|
541
|
+
const drive = await getDrive(`hyper://${hostname}/`, true)
|
|
495
542
|
|
|
496
543
|
return serveHead(drive, pathname, { accept, isRanged, noResolve })
|
|
497
544
|
}
|
|
@@ -577,7 +624,7 @@ export default async function makeHyperFetch ({
|
|
|
577
624
|
resHeaders[HEADER_LAST_MODIFIED] = date.toUTCString()
|
|
578
625
|
}
|
|
579
626
|
|
|
580
|
-
const size = entry.value.byteLength
|
|
627
|
+
const size = entry.value.blob.byteLength
|
|
581
628
|
if (isRanged) {
|
|
582
629
|
const ranges = parseRange(size, isRanged)
|
|
583
630
|
|
|
@@ -586,7 +633,7 @@ export default async function makeHyperFetch ({
|
|
|
586
633
|
const length = (end - start + 1)
|
|
587
634
|
|
|
588
635
|
return {
|
|
589
|
-
status:
|
|
636
|
+
status: 204,
|
|
590
637
|
headers: {
|
|
591
638
|
...resHeaders,
|
|
592
639
|
'Content-Length': `${length}`,
|
|
@@ -597,7 +644,7 @@ export default async function makeHyperFetch ({
|
|
|
597
644
|
}
|
|
598
645
|
|
|
599
646
|
return {
|
|
600
|
-
status:
|
|
647
|
+
status: 204,
|
|
601
648
|
headers: resHeaders
|
|
602
649
|
}
|
|
603
650
|
}
|
|
@@ -615,7 +662,7 @@ export default async function makeHyperFetch ({
|
|
|
615
662
|
const version = parts[3]
|
|
616
663
|
const realPath = ensureLeadingSlash(parts.slice(4).join('/'))
|
|
617
664
|
|
|
618
|
-
const drive = await getDrive(`hyper://${hostname}
|
|
665
|
+
const drive = await getDrive(`hyper://${hostname}/`, true)
|
|
619
666
|
|
|
620
667
|
const snapshot = await drive.checkout(version)
|
|
621
668
|
|
|
@@ -632,7 +679,7 @@ export default async function makeHyperFetch ({
|
|
|
632
679
|
const isRanged = request.headers.get('Range') || ''
|
|
633
680
|
const noResolve = searchParams.has('noResolve')
|
|
634
681
|
|
|
635
|
-
const drive = await getDrive(`hyper://${hostname}
|
|
682
|
+
const drive = await getDrive(`hyper://${hostname}/`, true)
|
|
636
683
|
|
|
637
684
|
return serveGet(drive, pathname, { accept, isRanged, noResolve })
|
|
638
685
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hypercore-fetch",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.7.0",
|
|
4
4
|
"description": "Implementation of Fetch that uses the Dat SDK for loading p2p content",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@rangermauve/fetch-event-source": "^1.0.3",
|
|
35
|
-
"hyper-sdk": "^4.
|
|
35
|
+
"hyper-sdk": "^4.3.0",
|
|
36
36
|
"standard": "^17.0.0",
|
|
37
37
|
"tape": "^5.2.2"
|
|
38
38
|
}
|
package/test.js
CHANGED
|
@@ -127,6 +127,7 @@ test('HEAD request', async (t) => {
|
|
|
127
127
|
const headersLastModified = headResponse.headers.get('Last-Modified')
|
|
128
128
|
const headersLink = headResponse.headers.get('Link')
|
|
129
129
|
|
|
130
|
+
t.equal(headResponse.status, 204, 'Response had expected status')
|
|
130
131
|
// Version at which the file was added
|
|
131
132
|
t.equal(headersEtag, '2', 'Headers got expected etag')
|
|
132
133
|
t.equal(headersContentType, 'text/plain; charset=utf-8', 'Headers got expected mime type')
|
|
@@ -296,6 +297,24 @@ test('DELETE a directory', async (t) => {
|
|
|
296
297
|
const entries = await listDirRequest.json()
|
|
297
298
|
t.deepEqual(entries, [], 'subfolder got deleted')
|
|
298
299
|
})
|
|
300
|
+
test.only('DELETE a drive from storage', async (t) => {
|
|
301
|
+
const created = await nextURL(t)
|
|
302
|
+
|
|
303
|
+
const uploadLocation = new URL('./subfolder/example.txt', created)
|
|
304
|
+
const uploadResponse = await fetch(uploadLocation, {
|
|
305
|
+
method: 'put',
|
|
306
|
+
body: SAMPLE_CONTENT
|
|
307
|
+
})
|
|
308
|
+
await checkResponse(uploadResponse, t)
|
|
309
|
+
|
|
310
|
+
const purgeResponse = await fetch(created, { method: 'delete' })
|
|
311
|
+
|
|
312
|
+
await checkResponse(purgeResponse, t, 'Able to purge')
|
|
313
|
+
|
|
314
|
+
const listDirRequest = await fetch(created)
|
|
315
|
+
|
|
316
|
+
t.notOk(listDirRequest.ok, 'Error when trying to read after purge')
|
|
317
|
+
})
|
|
299
318
|
test('Read index.html', async (t) => {
|
|
300
319
|
const created = await nextURL(t)
|
|
301
320
|
const uploadLocation = new URL('./index.html', created)
|
|
@@ -419,14 +438,6 @@ test('Resolve pretty markdown URLs', async (t) => {
|
|
|
419
438
|
t.equal(contentType, 'text/markdown; charset=utf-8', 'Got markdown mime type')
|
|
420
439
|
})
|
|
421
440
|
|
|
422
|
-
test('Doing a `GET` on an invalid domain should cause an error', async (t) => {
|
|
423
|
-
const url = 'hyper://example/'
|
|
424
|
-
|
|
425
|
-
const failedResponse = await fetch(url)
|
|
426
|
-
|
|
427
|
-
t.notOk(failedResponse.ok, 'Response errored out due to invalid domain')
|
|
428
|
-
})
|
|
429
|
-
|
|
430
441
|
test('EventSource extension messages', async (t) => {
|
|
431
442
|
const domain = await nextURL(t)
|
|
432
443
|
|
|
@@ -490,14 +501,13 @@ test('Resolve DNS', async (t) => {
|
|
|
490
501
|
t.ok(entries.length, 'Loaded contents with some files present')
|
|
491
502
|
})
|
|
492
503
|
|
|
493
|
-
test('
|
|
494
|
-
const
|
|
504
|
+
test('Doing a `GET` on an invalid domain/public key should cause an error', async (t) => {
|
|
505
|
+
const invalidDomainResponse = await fetch('hyper://example/')
|
|
506
|
+
t.notOk(invalidDomainResponse.ok, 'Response errored out due to invalid domain')
|
|
495
507
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
t.pass('Invalid names led to an error')
|
|
500
|
-
}
|
|
508
|
+
const invalidPublicKeyResponse = await fetch('hyper://aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/')
|
|
509
|
+
t.notOk(invalidPublicKeyResponse.ok, 'Response errored out due to invalid public key')
|
|
510
|
+
t.equal(invalidPublicKeyResponse.status, 404, 'Invalid public key should 404')
|
|
501
511
|
})
|
|
502
512
|
|
|
503
513
|
test('Old versions in VERSION folder', async (t) => {
|