hypercore-fetch 10.1.0 → 10.2.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 +20 -4
- package/index.js +110 -20
- package/package.json +1 -1
- package/test.js +1 -1
package/README.md
CHANGED
|
@@ -11,11 +11,13 @@ import * as SDK from 'hyper-sdk'
|
|
|
11
11
|
// Create in-memory hyper-sdk instance
|
|
12
12
|
const sdk = await SDK.create({storage: false})
|
|
13
13
|
|
|
14
|
+
// Init
|
|
14
15
|
const fetch = await makeFetch({
|
|
15
16
|
sdk: true,
|
|
16
17
|
writable: true
|
|
17
18
|
})
|
|
18
19
|
|
|
20
|
+
// Download
|
|
19
21
|
const someURL = `hyper://blog.mauve.moe/`
|
|
20
22
|
|
|
21
23
|
const response = await fetch(someURL)
|
|
@@ -23,6 +25,16 @@ const response = await fetch(someURL)
|
|
|
23
25
|
const data = await response.text()
|
|
24
26
|
|
|
25
27
|
console.log(data)
|
|
28
|
+
|
|
29
|
+
const response = await fetch('hyper://localhost/example.txt', {
|
|
30
|
+
method: 'PUT',
|
|
31
|
+
body: 'Hello World'
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
// This is where the data got uploaded
|
|
35
|
+
const location = response.headers.get('Location')
|
|
36
|
+
// Clear the response body or else electron will flip out
|
|
37
|
+
await response.text()
|
|
26
38
|
```
|
|
27
39
|
|
|
28
40
|
## API
|
|
@@ -104,6 +116,8 @@ In order to create a writable Hyperdrive with its own URL, you must first genera
|
|
|
104
116
|
|
|
105
117
|
`NAME` can be any alphanumeric string which can be used for key generation in [Corestore](https://github.com/holepunchto/corestore).
|
|
106
118
|
|
|
119
|
+
If you omit the `NAME`, it will use the name `default`.
|
|
120
|
+
|
|
107
121
|
The response body will contain a `hyper://` URL with the new Hyperdrive.
|
|
108
122
|
|
|
109
123
|
You can then use this with `PUT`/`DELETE` requests.
|
|
@@ -116,6 +130,8 @@ If you want to resolve the public key URL of a previously created Hyperdrive, yo
|
|
|
116
130
|
|
|
117
131
|
`NAME` can be any alphanumeric string which can be used for key generation in [Corestore](https://github.com/holepunchto/corestore).
|
|
118
132
|
|
|
133
|
+
If you omit the `NAME`, it will use the name `default`.
|
|
134
|
+
|
|
119
135
|
The response body will contain a `hyper://` URL with the new Hyperdrive.
|
|
120
136
|
|
|
121
137
|
You can then use this with `PUT`/`DELETE` requests.
|
|
@@ -130,7 +146,7 @@ flag.
|
|
|
130
146
|
|
|
131
147
|
The `body` can be any of the options supported by the Fetch API such as a `String`, `Blob`, `FormData`, or `ReadableStream`.
|
|
132
148
|
|
|
133
|
-
`NAME` can either be the 52 character [z32 encoded](https://github.com/mafintosh/z32) key for a Hyperdrive
|
|
149
|
+
`NAME` can either be the 52 character [z32 encoded](https://github.com/mafintosh/z32) key for a Hyperdrive, `localhost` for the `default` drive, or a domain to parse with the [DNSLink](https://www.dnslink.io/) standard.
|
|
134
150
|
|
|
135
151
|
The mtime metadata is automatically set to the current time when
|
|
136
152
|
uploading. To override this value, pass a `Last-Modified` header with a value
|
|
@@ -149,7 +165,7 @@ The `filename` will be the filename within the directory that gets created.
|
|
|
149
165
|
|
|
150
166
|
Note that you must use the name `file` for uploaded files.
|
|
151
167
|
|
|
152
|
-
`NAME` can either be the 52 character [z32 encoded](https://github.com/mafintosh/z32) key for a Hyperdrive
|
|
168
|
+
`NAME` can either be the 52 character [z32 encoded](https://github.com/mafintosh/z32) key for a Hyperdrive, `localhost` for the `default` drive, or a domain to parse with the [DNSLink](https://www.dnslink.io/) standard.
|
|
153
169
|
|
|
154
170
|
### `fetch('hyper://NAME/', {method: 'DELETE'})`
|
|
155
171
|
|
|
@@ -159,13 +175,13 @@ If this is a writable drive, your data will get fully clearned and trying to wri
|
|
|
159
175
|
|
|
160
176
|
If you try to load this drive again data will be loaded from scratch.
|
|
161
177
|
|
|
162
|
-
`NAME` can either be the 52 character [z32 encoded](https://github.com/mafintosh/z32) key for a Hyperdrive
|
|
178
|
+
`NAME` can either be the 52 character [z32 encoded](https://github.com/mafintosh/z32) key for a Hyperdrive, `localhost` for the `default` drive, or a domain to parse with the [DNSLink](https://www.dnslink.io/) standard.
|
|
163
179
|
|
|
164
180
|
### `fetch('hyper://NAME/example.txt', {method: 'DELETE'})`
|
|
165
181
|
|
|
166
182
|
You can delete a file or directory tree in a Hyperdrive by using the `DELETE` method.
|
|
167
183
|
|
|
168
|
-
`NAME` can either be the 52 character [z32 encoded](https://github.com/mafintosh/z32) key for a Hyperdrive
|
|
184
|
+
`NAME` can either be the 52 character [z32 encoded](https://github.com/mafintosh/z32) key for a Hyperdrive, `localhost` for the `default` drive, or a domain to parse with the [DNSLink](https://www.dnslink.io/) standard.
|
|
169
185
|
|
|
170
186
|
Note that this is only available with the `writable: true` flag.
|
|
171
187
|
|
package/index.js
CHANGED
|
@@ -15,6 +15,7 @@ import { EventIterator } from 'event-iterator'
|
|
|
15
15
|
/** @typedef {(url: URL) => void} OnDeleteHandler */
|
|
16
16
|
|
|
17
17
|
const DEFAULT_TIMEOUT = 5000
|
|
18
|
+
const DEFAULT_DRIVE = 'default'
|
|
18
19
|
|
|
19
20
|
const SPECIAL_DOMAIN = 'localhost'
|
|
20
21
|
const SPECIAL_FOLDER = '$'
|
|
@@ -83,6 +84,7 @@ function noop () {}
|
|
|
83
84
|
* @param {boolean} [options.writable]
|
|
84
85
|
* @param {boolean} [options.extensionMessages]
|
|
85
86
|
* @param {number} [options.timeout]
|
|
87
|
+
* @param {string} [options.defaultDrive]
|
|
86
88
|
* @param {typeof DEFAULT_RENDER_INDEX} [options.renderIndex]
|
|
87
89
|
* @param {OnLoadHandler} [options.onLoad]
|
|
88
90
|
* @param {OnDeleteHandler} [options.onDelete]
|
|
@@ -93,6 +95,7 @@ export default async function makeHyperFetch ({
|
|
|
93
95
|
writable = false,
|
|
94
96
|
extensionMessages = writable,
|
|
95
97
|
timeout = DEFAULT_TIMEOUT,
|
|
98
|
+
defaultDrive = DEFAULT_DRIVE,
|
|
96
99
|
renderIndex = DEFAULT_RENDER_INDEX,
|
|
97
100
|
onLoad = noop,
|
|
98
101
|
onDelete = noop
|
|
@@ -115,6 +118,10 @@ export default async function makeHyperFetch ({
|
|
|
115
118
|
if (writable) {
|
|
116
119
|
router.get(`hyper://${SPECIAL_DOMAIN}/`, getKey)
|
|
117
120
|
router.post(`hyper://${SPECIAL_DOMAIN}/`, createKey)
|
|
121
|
+
router.put(`hyper://${SPECIAL_DOMAIN}/`, putFilesDefault)
|
|
122
|
+
router.delete('hyper://SPECIAL_DOMAIN/**', deleteFilesDefault)
|
|
123
|
+
router.get(`hyper://${SPECIAL_DOMAIN}/**`, getFilesDefault)
|
|
124
|
+
router.head(`hyper://${SPECIAL_DOMAIN}/**`, headFilesDefault)
|
|
118
125
|
|
|
119
126
|
router.put(`hyper://*/${SPECIAL_FOLDER}/${VERSION_FOLDER_NAME}/**`, putFilesVersioned)
|
|
120
127
|
router.put('hyper://*/**', putFiles)
|
|
@@ -345,10 +352,7 @@ export default async function makeHyperFetch ({
|
|
|
345
352
|
* @returns
|
|
346
353
|
*/
|
|
347
354
|
async function getKey (request) {
|
|
348
|
-
const key = new URL(request.url).searchParams.get('key')
|
|
349
|
-
if (!key) {
|
|
350
|
-
return { status: 400, body: 'Must specify key parameter to resolve' }
|
|
351
|
-
}
|
|
355
|
+
const key = new URL(request.url).searchParams.get('key') || defaultDrive
|
|
352
356
|
|
|
353
357
|
try {
|
|
354
358
|
const drive = await sdk.getDrive(key)
|
|
@@ -378,10 +382,7 @@ export default async function makeHyperFetch ({
|
|
|
378
382
|
// Maybe specify a seed to use for generating the blobs?
|
|
379
383
|
// Else we'd need to specify the blobs keys and metadata keys
|
|
380
384
|
|
|
381
|
-
const key = new URL(request.url).searchParams.get('key')
|
|
382
|
-
if (!key) {
|
|
383
|
-
return { status: 400, body: 'Must specify key parameter to resolve' }
|
|
384
|
-
}
|
|
385
|
+
const key = new URL(request.url).searchParams.get('key') || defaultDrive
|
|
385
386
|
|
|
386
387
|
const drive = await sdk.getDrive(key)
|
|
387
388
|
onLoad(new URL('/', drive.url), drive.writable, key)
|
|
@@ -389,12 +390,30 @@ export default async function makeHyperFetch ({
|
|
|
389
390
|
return { body: drive.url }
|
|
390
391
|
}
|
|
391
392
|
|
|
393
|
+
/**
|
|
394
|
+
* @param {Request} request
|
|
395
|
+
*/
|
|
396
|
+
async function putFilesDefault (request) {
|
|
397
|
+
const finalURL = await resolveInDefault(request.url)
|
|
398
|
+
|
|
399
|
+
return putTo(finalURL, request)
|
|
400
|
+
}
|
|
401
|
+
|
|
392
402
|
/**
|
|
393
403
|
* @param {Request} request
|
|
394
404
|
* @returns
|
|
395
405
|
*/
|
|
396
406
|
async function putFiles (request) {
|
|
397
|
-
|
|
407
|
+
return putTo(request.url, request)
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* @param {string} url
|
|
412
|
+
* @param {Request} request
|
|
413
|
+
* @returns
|
|
414
|
+
*/
|
|
415
|
+
async function putTo (url, request) {
|
|
416
|
+
const { hostname, pathname: rawPathname } = new URL(url)
|
|
398
417
|
const pathname = decodeURI(ensureLeadingSlash(rawPathname))
|
|
399
418
|
const contentType = request.headers.get('Content-Type') || ''
|
|
400
419
|
const lastModified = request.headers.get('Last-Modified')
|
|
@@ -407,7 +426,7 @@ export default async function makeHyperFetch ({
|
|
|
407
426
|
return { status: 403, body: `Cannot PUT file to read-only drive: ${drive.url}`, headers: { Location: request.url } }
|
|
408
427
|
}
|
|
409
428
|
|
|
410
|
-
onLoad(new URL('/',
|
|
429
|
+
onLoad(new URL('/', url), drive.writable)
|
|
411
430
|
|
|
412
431
|
if (isFormData) {
|
|
413
432
|
// It's a form! Get the files out and process them
|
|
@@ -427,7 +446,7 @@ export default async function makeHyperFetch ({
|
|
|
427
446
|
}
|
|
428
447
|
} else {
|
|
429
448
|
if (pathname.endsWith('/')) {
|
|
430
|
-
return { status: 405, body: 'Cannot PUT file with trailing slash', headers: { Location:
|
|
449
|
+
return { status: 405, body: 'Cannot PUT file with trailing slash', headers: { Location: url } }
|
|
431
450
|
} else {
|
|
432
451
|
await pipelinePromise(
|
|
433
452
|
Readable.from(request.body),
|
|
@@ -478,20 +497,39 @@ export default async function makeHyperFetch ({
|
|
|
478
497
|
return { status: 200 }
|
|
479
498
|
}
|
|
480
499
|
|
|
500
|
+
/**
|
|
501
|
+
* @param {Request} request
|
|
502
|
+
* @returns
|
|
503
|
+
*/
|
|
504
|
+
async function deleteFilesDefault (request) {
|
|
505
|
+
const finalURL = await resolveInDefault(request.url)
|
|
506
|
+
|
|
507
|
+
return deleteFilesTo(finalURL, request)
|
|
508
|
+
}
|
|
509
|
+
|
|
481
510
|
/**
|
|
482
511
|
* @param {Request} request
|
|
483
512
|
* @returns
|
|
484
513
|
*/
|
|
485
514
|
async function deleteFiles (request) {
|
|
486
|
-
|
|
515
|
+
return deleteFilesTo(request.url, request)
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* @param {string} url
|
|
520
|
+
* @param {Request} request
|
|
521
|
+
* @returns
|
|
522
|
+
*/
|
|
523
|
+
async function deleteFilesTo (url, request) {
|
|
524
|
+
const { hostname, pathname: rawPathname } = new URL(url)
|
|
487
525
|
const pathname = decodeURI(ensureLeadingSlash(rawPathname))
|
|
488
526
|
|
|
489
527
|
const drive = await sdk.getDrive(`hyper://${hostname}/`)
|
|
490
528
|
if (!drive.writable) {
|
|
491
|
-
return { status: 403, body: `Cannot DELETE file in read-only drive: ${drive.url}`, headers: { Location:
|
|
529
|
+
return { status: 403, body: `Cannot DELETE file in read-only drive: ${drive.url}`, headers: { Location: url } }
|
|
492
530
|
}
|
|
493
531
|
|
|
494
|
-
onLoad(new URL('/',
|
|
532
|
+
onLoad(new URL('/', url), drive.writable)
|
|
495
533
|
|
|
496
534
|
if (pathname.endsWith('/')) {
|
|
497
535
|
let didDelete = false
|
|
@@ -561,12 +599,31 @@ export default async function makeHyperFetch ({
|
|
|
561
599
|
return serveHead(snapshot, realPath, { accept, isRanged, noResolve })
|
|
562
600
|
}
|
|
563
601
|
|
|
602
|
+
/**
|
|
603
|
+
* @param {Request} request
|
|
604
|
+
* @returns
|
|
605
|
+
*/
|
|
606
|
+
async function headFilesDefault (request) {
|
|
607
|
+
const finalURL = await resolveInDefault(request.url)
|
|
608
|
+
|
|
609
|
+
return headFilesFrom(finalURL, request)
|
|
610
|
+
}
|
|
611
|
+
|
|
564
612
|
/**
|
|
565
613
|
* @param {Request} request
|
|
566
614
|
* @returns
|
|
567
615
|
*/
|
|
568
616
|
async function headFiles (request) {
|
|
569
|
-
|
|
617
|
+
return headFilesFrom(request.url, request)
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* @param {string} _url
|
|
622
|
+
* @param {Request} request
|
|
623
|
+
* @returns
|
|
624
|
+
*/
|
|
625
|
+
async function headFilesFrom (_url, request) {
|
|
626
|
+
const url = new URL(_url)
|
|
570
627
|
const { hostname, pathname: rawPathname, searchParams } = url
|
|
571
628
|
const pathname = decodeURI(ensureLeadingSlash(rawPathname))
|
|
572
629
|
|
|
@@ -738,12 +795,31 @@ export default async function makeHyperFetch ({
|
|
|
738
795
|
}
|
|
739
796
|
|
|
740
797
|
/**
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
798
|
+
* @param {Request} request
|
|
799
|
+
* @returns
|
|
800
|
+
*/
|
|
801
|
+
async function getFilesDefault (request) {
|
|
802
|
+
const finalURL = await resolveInDefault(request.url)
|
|
803
|
+
|
|
804
|
+
return getFilesFrom(finalURL, request)
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
/**
|
|
808
|
+
* @param {Request} request
|
|
809
|
+
* @returns
|
|
810
|
+
*/
|
|
744
811
|
async function getFiles (request) {
|
|
812
|
+
return getFilesFrom(request.url, request)
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
/**
|
|
816
|
+
* @param {string} _url
|
|
817
|
+
* @param {Request} request
|
|
818
|
+
* @returns
|
|
819
|
+
*/
|
|
820
|
+
async function getFilesFrom (_url, request) {
|
|
745
821
|
// TODO: Redirect on directories without trailing slash
|
|
746
|
-
const url = new URL(
|
|
822
|
+
const url = new URL(_url)
|
|
747
823
|
const { hostname, pathname: rawPathname, searchParams } = url
|
|
748
824
|
const pathname = decodeURI(ensureLeadingSlash(rawPathname))
|
|
749
825
|
|
|
@@ -760,7 +836,7 @@ export default async function makeHyperFetch ({
|
|
|
760
836
|
}
|
|
761
837
|
}
|
|
762
838
|
|
|
763
|
-
onLoad(new URL('/',
|
|
839
|
+
onLoad(new URL('/', url), drive.writable)
|
|
764
840
|
|
|
765
841
|
return serveGet(drive, pathname, { accept, isRanged, noResolve })
|
|
766
842
|
}
|
|
@@ -836,6 +912,20 @@ export default async function makeHyperFetch ({
|
|
|
836
912
|
return serveFile(drive, path, isRanged)
|
|
837
913
|
}
|
|
838
914
|
|
|
915
|
+
/**
|
|
916
|
+
* Resolve a URL with a pathname to be within the default drive
|
|
917
|
+
* @param {string} url
|
|
918
|
+
* @returns {Promise<string>}
|
|
919
|
+
*/
|
|
920
|
+
async function resolveInDefault (url) {
|
|
921
|
+
const { pathname } = new URL(url)
|
|
922
|
+
|
|
923
|
+
const drive = await sdk.getDrive(defaultDrive)
|
|
924
|
+
const finalURL = new URL(pathname, drive.url).href
|
|
925
|
+
|
|
926
|
+
return finalURL
|
|
927
|
+
}
|
|
928
|
+
|
|
839
929
|
return fetch
|
|
840
930
|
}
|
|
841
931
|
|
package/package.json
CHANGED
package/test.js
CHANGED
|
@@ -669,7 +669,7 @@ test('Check hyperdrive writability', async (t) => {
|
|
|
669
669
|
t.equal(writableHeadersAllow, 'HEAD,GET,PUT,DELETE', 'Expected writable Allows header')
|
|
670
670
|
})
|
|
671
671
|
|
|
672
|
-
test
|
|
672
|
+
test('onLoad and onDelete handlers', async (t) => {
|
|
673
673
|
/** @type {Parameters<OnLoadHandler> | Parameters<OnDeleteHandler> | null} */
|
|
674
674
|
let args = null
|
|
675
675
|
|