Compare commits

..

159 Commits

Author SHA1 Message Date
Derrick Hammer 37789394f4
fix: remove hash import 2024-03-28 20:30:06 -04:00
Derrick Hammer 6ba0e4af07
fix: pass file directly, not as a json payload 2024-03-27 23:40:45 -04:00
Derrick Hammer 17c3addeb2
fix: reverse array before popping to get the 1st element 2024-03-26 23:48:44 -04:00
Derrick Hammer a91b58924b
fix: ensure we don't have a nested array-like object with the data 2024-03-26 11:45:37 -04:00
Derrick Hammer f85bd2b728
fix: don't use hash import 2024-03-22 18:36:08 -04:00
Derrick Hammer f77af25b84
feat: add pinStatus api 2024-03-22 18:12:07 -04:00
Derrick Hammer 645df0c8d0
feat: add pin and unpin api 2024-03-22 18:11:17 -04:00
Derrick Hammer fb8a1b3c7d
core: update swagger.yaml 2024-03-22 18:02:56 -04:00
Derrick Hammer afb3d7fa3d
core: update swagger.yaml 2024-03-21 16:29:13 -04:00
Derrick Hammer b6f335bc09
refactor: switch to using abort controller, and allow signal to be overridden 2024-03-21 16:04:20 -04:00
Derrick Hammer 6bdc43dd85
feat: add httpConfig to CustomClientOptions and allow it to override anything via optionsToConfig 2024-03-21 16:01:10 -04:00
Derrick Hammer c1b55c5d98
fix: remove more S5Client param references 2024-03-21 15:15:08 -04:00
Derrick Hammer 300f6876c6
fix: don't use hash imports 2024-03-21 13:18:29 -04:00
Derrick Hammer a2bdba0599
fix: @noble/curves imports 2024-03-21 13:13:54 -04:00
Derrick Hammer 6a02b9f256
fix: remove hash imports 2024-03-21 13:07:52 -04:00
Derrick Hammer a53e54049d
dep: update libs5 2024-03-21 12:54:34 -04:00
Derrick Hammer c32edef621
core: update swagger.yaml 2024-03-21 12:36:58 -04:00
Derrick Hammer f31b343aa0
fix: don't use hash import 2024-03-21 12:19:43 -04:00
Derrick Hammer 5bee4f9e8c
chore: unused opts 2024-03-21 12:09:38 -04:00
Derrick Hammer 86ce3874ec
refactor: remove S5Client from parameters 2024-03-21 12:08:41 -04:00
Derrick Hammer f2d4a146c5
refactor: add CancelablePromise sub-type so we can call promise.cancel 2024-03-21 11:36:32 -04:00
Derrick Hammer 0f6495afa8
fix: set hash and mime_type as required 2024-03-21 11:01:47 -04:00
Derrick Hammer dabaaf3ced
fix: set pins as required 2024-03-21 10:55:21 -04:00
Derrick Hammer 4561e06b61
refactor: throw a custom S5Error 2024-03-21 10:47:14 -04:00
Derrick Hammer dfe79e2fc2
refactor: merge all methods into a single class definition 2024-03-21 10:22:50 -04:00
Derrick Hammer da7c405905
style: prettier 2024-03-18 18:40:26 -04:00
Derrick Hammer 560c7c374e
fix: export HashProgressEvent 2024-03-18 18:40:08 -04:00
Derrick Hammer 268df87b63
feat: add onHashProgress 2024-03-18 18:29:46 -04:00
Derrick Hammer a9cd7c54a9
dep: update @lumeweb/libs5 2024-03-18 18:03:29 -04:00
Derrick Hammer 2b2b846c45
dep: update @lumeweb/libs5 2024-03-18 17:58:39 -04:00
Derrick Hammer 8846a23b8a
fix: cid is lowercase 2024-03-18 14:29:30 -04:00
Derrick Hammer 2ee6124637
chore: update swagger.yaml 2024-03-18 14:28:16 -04:00
Derrick Hammer e86187ac77
dep: update 2024-03-18 11:34:11 -04:00
Derrick Hammer c157999fd3
fix: need to patch tsc-alias 2024-03-18 11:32:58 -04:00
Derrick Hammer b33c856654
dep: add patch-package 2024-03-18 11:32:42 -04:00
Derrick Hammer 8e368e4e0c
fix: dont use hash imports 2024-03-18 11:16:40 -04:00
Derrick Hammer 61c9ef880a
fix: dont use hash imports 2024-03-18 11:10:20 -04:00
Derrick Hammer faf7492ff8
fix: dont use hash imports 2024-03-18 11:02:30 -04:00
Derrick Hammer 12dd703bdb
dep: add url-parse 2024-03-18 11:00:01 -04:00
Derrick Hammer 457c8081a3
fix: dont use hash imports 2024-03-18 10:59:49 -04:00
Derrick Hammer 07048c526b
fix: update imports 2024-03-18 09:21:05 -04:00
Derrick Hammer c34a6ef094
fix: export CustomClientOptions 2024-03-18 09:18:59 -04:00
Derrick Hammer eb242bd3c7
feat: add clientOptions setter 2024-03-18 09:14:44 -04:00
Derrick Hammer 1eb9ebbdf8
refactor: rename customOptions to clientOptions 2024-03-18 09:14:34 -04:00
Derrick Hammer 5bc2915655
style: prettier 2024-03-18 09:10:07 -04:00
Derrick Hammer 7bc48484a2
refactor: rewrite header handling, lowercase apiKey, and cleanup 2024-03-18 09:09:20 -04:00
Derrick Hammer ba370250a2
refactor: split off tus upload to getTusOptions so it can be reusable to external libraries 2024-03-17 14:32:59 -04:00
Derrick Hammer b4d1e0ec67
dep: add p-defer 2024-03-17 14:04:11 -04:00
Derrick Hammer 0dd72358da
feat: add onUploadProgress to CustomUploadOptions 2024-03-17 13:33:37 -04:00
Derrick Hammer b1179a868f
ci: add changesets 2024-03-16 12:06:21 -04:00
Derrick Hammer 64a453923a
refactor: use full mime lib 2024-03-16 12:03:42 -04:00
Derrick Hammer f22a7ba511
dep: update mime 2024-03-16 12:03:17 -04:00
Derrick Hammer 54ca99d8d1
build: change compilerOptions.module to ESNext 2024-03-16 11:49:15 -04:00
Derrick Hammer 83b7d78af1
feat: add account pins api 2024-03-16 11:44:32 -04:00
Derrick Hammer 7cdcfe3b3e
chore: update swagger.yaml 2024-03-16 11:38:04 -04:00
Derrick Hammer e9f4b03979
fix: add download methods to client 2024-02-13 00:30:13 -05:00
Derrick Hammer 9510735005
chore: remove subdomain 2024-02-13 00:28:04 -05:00
Derrick Hammer 853db73634
feat: add downloadBlob method 2024-02-13 00:27:33 -05:00
Derrick Hammer 3c9022d8c7
fix: dont pass responseType, as we want a blob 2024-02-13 00:25:47 -05:00
Derrick Hammer bc6d545501
fix: use DEFAULT_DOWNLOAD_OPTIONS 2024-02-13 00:09:52 -05:00
Derrick Hammer 39ccac179d
feat: add downloadProof method 2024-02-13 00:08:42 -05:00
Derrick Hammer 8e172ebef9
fix: append auth token to url 2024-02-13 00:01:34 -05:00
Derrick Hammer 10e41875e6
refactor: major refactor to use openapi via orval 2024-02-12 23:25:48 -05:00
Derrick Hammer dd98f51f4b
Merge remote-tracking branch 'origin/develop' into develop 2024-02-12 19:53:39 -05:00
Derrick Hammer 109278021d
ci: remove semantic-release and github actions 2024-02-12 19:53:00 -05:00
semantic-release-bot 89cf5b398c chore(release): 0.1.0-develop.29 [skip ci]
# [0.1.0-develop.29](https://git.lumeweb.com/LumeWeb/s5-js/compare/v0.1.0-develop.28...v0.1.0-develop.29) (2024-01-10)

### Bug Fixes

* set binaryType ([d1ce4a1](d1ce4a1ae0))
2024-01-10 08:19:17 +00:00
Derrick Hammer 8be4e311fd
Merge remote-tracking branch 'origin/develop' into develop 2024-01-10 03:18:36 -05:00
Derrick Hammer d1ce4a1ae0
fix: set binaryType 2024-01-10 03:18:32 -05:00
semantic-release-bot 42cbb29692 chore(release): 0.1.0-develop.28 [skip ci]
# [0.1.0-develop.28](https://git.lumeweb.com/LumeWeb/s5-js/compare/v0.1.0-develop.27...v0.1.0-develop.28) (2024-01-10)

### Bug Fixes

* use addEventListener, on is not available in all environments ([c4c0a94](c4c0a947a9))
2024-01-10 07:55:18 +00:00
Derrick Hammer 11a033b3f7
Merge remote-tracking branch 'origin/develop' into develop 2024-01-10 02:54:25 -05:00
Derrick Hammer c4c0a947a9
fix: use addEventListener, on is not available in all environments 2024-01-10 02:54:20 -05:00
semantic-release-bot b97a212f57 chore(release): 0.1.0-develop.27 [skip ci]
# [0.1.0-develop.27](https://git.lumeweb.com/LumeWeb/s5-js/compare/v0.1.0-develop.26...v0.1.0-develop.27) (2023-12-28)
2023-12-28 08:58:38 +00:00
Derrick Hammer 7d466b17c3
Merge remote-tracking branch 'origin/develop' into develop 2023-12-28 03:57:50 -05:00
Derrick Hammer 115caeeaee
dep: update libs5 2023-12-28 03:57:42 -05:00
semantic-release-bot a697be1865 chore(release): 0.1.0-develop.26 [skip ci]
# [0.1.0-develop.26](https://git.lumeweb.com/LumeWeb/s5-js/compare/v0.1.0-develop.25...v0.1.0-develop.26) (2023-12-28)

### Bug Fixes

* use on, once is not available in all environments ([d78ad5e](d78ad5eb29))
2023-12-28 08:05:33 +00:00
Derrick Hammer 27d739a816
Merge remote-tracking branch 'origin/develop' into develop 2023-12-28 03:04:32 -05:00
Derrick Hammer d78ad5eb29
fix: use on, once is not available in all environments 2023-12-28 03:04:24 -05:00
semantic-release-bot 4125ff524a chore(release): 0.1.0-develop.25 [skip ci]
# [0.1.0-develop.25](https://git.lumeweb.com/LumeWeb/s5-js/compare/v0.1.0-develop.24...v0.1.0-develop.25) (2023-12-28)
2023-12-28 06:25:19 +00:00
Derrick Hammer f66969c698
Merge remote-tracking branch 'origin/develop' into develop 2023-12-28 01:24:30 -05:00
Derrick Hammer ffad64a787
dep: add multiformats 2023-12-28 01:24:26 -05:00
semantic-release-bot f3365838d9 chore(release): 0.1.0-develop.24 [skip ci]
# [0.1.0-develop.24](https://git.lumeweb.com/LumeWeb/s5-js/compare/v0.1.0-develop.23...v0.1.0-develop.24) (2023-12-18)

### Bug Fixes

* ensure we are using a ws protocol ([bb7c7dc](bb7c7dcece))
2023-12-18 06:31:55 +00:00
Derrick Hammer c3377135b6
Merge remote-tracking branch 'origin/develop' into develop 2023-12-18 01:21:17 -05:00
Derrick Hammer bb7c7dcece
fix: ensure we are using a ws protocol 2023-12-18 01:21:13 -05:00
semantic-release-bot ef072d01b4 chore(release): 0.1.0-develop.23 [skip ci]
# [0.1.0-develop.23](https://git.lumeweb.com/LumeWeb/s5-js/compare/v0.1.0-develop.22...v0.1.0-develop.23) (2023-12-17)

### Bug Fixes

* return GetMetadataResponse ([fcdd42f](fcdd42f2a3))
2023-12-17 22:14:01 +00:00
Derrick Hammer 36529bfe06
Merge remote-tracking branch 'origin/develop' into develop 2023-12-17 17:13:22 -05:00
Derrick Hammer fcdd42f2a3
fix: return GetMetadataResponse 2023-12-17 17:13:18 -05:00
semantic-release-bot 3a5d96f3be chore(release): 0.1.0-develop.22 [skip ci]
# [0.1.0-develop.22](https://git.lumeweb.com/LumeWeb/s5-js/compare/v0.1.0-develop.21...v0.1.0-develop.22) (2023-12-17)

### Bug Fixes

* need to pass endpointPath ([bf23047](bf230478b7))
2023-12-17 22:05:17 +00:00
Derrick Hammer 9f59a2389d
Merge remote-tracking branch 'origin/develop' into develop 2023-12-17 17:04:34 -05:00
Derrick Hammer bf230478b7
fix: need to pass endpointPath 2023-12-17 17:04:29 -05:00
semantic-release-bot e3b3f319a8 chore(release): 0.1.0-develop.21 [skip ci]
# [0.1.0-develop.21](https://git.lumeweb.com/LumeWeb/s5-js/compare/v0.1.0-develop.20...v0.1.0-develop.21) (2023-12-17)

### Bug Fixes

* on existing entry, need to update the entry data ([389a15e](389a15eef4))
2023-12-17 21:56:22 +00:00
Derrick Hammer f330c673f4
Merge remote-tracking branch 'origin/develop' into develop 2023-12-17 16:55:40 -05:00
Derrick Hammer 389a15eef4
fix: on existing entry, need to update the entry data 2023-12-17 16:55:35 -05:00
Derrick Hammer c61f2f6127
refactor: use a custom instance od axios per client 2023-12-17 16:50:02 -05:00
semantic-release-bot a6e544bebb chore(release): 0.1.0-develop.20 [skip ci]
# [0.1.0-develop.20](https://git.lumeweb.com/LumeWeb/s5-js/compare/v0.1.0-develop.19...v0.1.0-develop.20) (2023-12-12)

### Bug Fixes

* ensure we prefix the CID ed22519 type to the registry pubkey ([e4dc098](e4dc098547))
2023-12-12 21:32:54 +00:00
Derrick Hammer edda00090a
Merge remote-tracking branch 'origin/develop' into develop 2023-12-12 16:32:09 -05:00
Derrick Hammer e4dc098547
fix: ensure we prefix the CID ed22519 type to the registry pubkey 2023-12-12 16:32:05 -05:00
semantic-release-bot c6be47605d chore(release): 0.1.0-develop.19 [skip ci]
# [0.1.0-develop.19](https://git.lumeweb.com/LumeWeb/s5-js/compare/v0.1.0-develop.18...v0.1.0-develop.19) (2023-12-12)
2023-12-12 21:13:29 +00:00
Derrick Hammer 55e07bb689
Merge remote-tracking branch 'origin/develop' into develop 2023-12-12 16:12:38 -05:00
Derrick Hammer 6388669ca8
refactor: bug fixes and return early if the entry is the same 2023-12-12 16:12:34 -05:00
semantic-release-bot 12d54f454f chore(release): 0.1.0-develop.18 [skip ci]
# [0.1.0-develop.18](https://git.lumeweb.com/LumeWeb/s5-js/compare/v0.1.0-develop.17...v0.1.0-develop.18) (2023-12-12)

### Bug Fixes

* need to pass as query, not data ([820cc5b](820cc5bd89))
2023-12-12 21:06:11 +00:00
Derrick Hammer 9ecea29244
Merge remote-tracking branch 'origin/develop' into develop 2023-12-12 16:05:23 -05:00
Derrick Hammer 820cc5bd89
fix: need to pass as query, not data 2023-12-12 16:05:19 -05:00
semantic-release-bot 881ad76d76 chore(release): 0.1.0-develop.17 [skip ci]
# [0.1.0-develop.17](https://git.lumeweb.com/LumeWeb/s5-js/compare/v0.1.0-develop.16...v0.1.0-develop.17) (2023-12-12)

### Bug Fixes

* use get not post ([e24d352](e24d352d4e))
2023-12-12 21:01:06 +00:00
Derrick Hammer 17d594265c
Merge remote-tracking branch 'origin/develop' into develop 2023-12-12 15:54:00 -05:00
Derrick Hammer e24d352d4e
fix: use get not post 2023-12-12 15:53:55 -05:00
semantic-release-bot 236bae5a90 chore(release): 0.1.0-develop.16 [skip ci]
# [0.1.0-develop.16](https://git.lumeweb.com/LumeWeb/s5-js/compare/v0.1.0-develop.15...v0.1.0-develop.16) (2023-12-12)

### Features

* add getEntry and update createEntry to use it ([aea2360](aea236067e))
2023-12-12 20:49:17 +00:00
Derrick Hammer 68f9c91c62
Merge remote-tracking branch 'origin/develop' into develop 2023-12-12 15:48:19 -05:00
Derrick Hammer aea236067e
feat: add getEntry and update createEntry to use it 2023-12-12 15:48:13 -05:00
semantic-release-bot 0239aacef6 chore(release): 0.1.0-develop.15 [skip ci]
# [0.1.0-develop.15](https://git.lumeweb.com/LumeWeb/s5-js/compare/v0.1.0-develop.14...v0.1.0-develop.15) (2023-12-12)

### Bug Fixes

* need to use toRegistryEntry ([eb3f2ee](eb3f2ee5c9))
2023-12-12 19:54:28 +00:00
Derrick Hammer 6a3eea92b7
Merge remote-tracking branch 'origin/develop' into develop 2023-12-12 14:53:38 -05:00
Derrick Hammer eb3f2ee5c9
fix: need to use toRegistryEntry 2023-12-12 14:53:34 -05:00
semantic-release-bot 03cea1aec4 chore(release): 0.1.0-develop.14 [skip ci]
# [0.1.0-develop.14](https://git.lumeweb.com/LumeWeb/s5-js/compare/v0.1.0-develop.13...v0.1.0-develop.14) (2023-12-12)

### Bug Fixes

* need to strip off multiformats prefix ([502c8fb](502c8fb079))
2023-12-12 19:41:40 +00:00
Derrick Hammer a86fdff26b
Merge remote-tracking branch 'origin/develop' into develop 2023-12-12 14:40:57 -05:00
Derrick Hammer 502c8fb079
fix: need to strip off multiformats prefix 2023-12-12 14:40:53 -05:00
semantic-release-bot e0038fc095 chore(release): 0.1.0-develop.13 [skip ci]
# [0.1.0-develop.13](https://git.lumeweb.com/LumeWeb/s5-js/compare/v0.1.0-develop.12...v0.1.0-develop.13) (2023-12-12)

### Bug Fixes

* Revert "fix: use base64urlpad" ([74407cf](74407cf9a1))
2023-12-12 19:35:49 +00:00
Derrick Hammer c358fa3acc
Merge remote-tracking branch 'origin/develop' into develop 2023-12-12 14:34:55 -05:00
Derrick Hammer 74407cf9a1
fix: Revert "fix: use base64urlpad"
This reverts commit ce47696d90.
2023-12-12 14:34:19 -05:00
semantic-release-bot 8ecc0d93be chore(release): 0.1.0-develop.12 [skip ci]
# [0.1.0-develop.12](https://git.lumeweb.com/LumeWeb/s5-js/compare/v0.1.0-develop.11...v0.1.0-develop.12) (2023-12-12)

### Bug Fixes

* use base64urlpad ([ce47696](ce47696d90))
2023-12-12 04:47:33 +00:00
Derrick Hammer a577ee694d
Merge remote-tracking branch 'origin/develop' into develop 2023-12-11 23:46:49 -05:00
Derrick Hammer ce47696d90
fix: use base64urlpad 2023-12-11 23:46:41 -05:00
semantic-release-bot 08aeb6bb64 chore(release): 0.1.0-develop.11 [skip ci]
# [0.1.0-develop.11](https://git.lumeweb.com/LumeWeb/s5-js/compare/v0.1.0-develop.10...v0.1.0-develop.11) (2023-12-12)
2023-12-12 04:01:11 +00:00
Derrick Hammer 354506bbb1
Merge remote-tracking branch 'origin/develop' into develop 2023-12-11 23:00:31 -05:00
Derrick Hammer fd959c91df
dep: update libs5 2023-12-11 23:00:26 -05:00
semantic-release-bot 254cba9349 chore(release): 0.1.0-develop.10 [skip ci]
# [0.1.0-develop.10](https://git.lumeweb.com/LumeWeb/s5-js/compare/v0.1.0-develop.9...v0.1.0-develop.10) (2023-12-12)
2023-12-12 03:55:50 +00:00
Derrick Hammer e5559c75b3
Merge remote-tracking branch 'origin/develop' into develop 2023-12-11 22:55:06 -05:00
Derrick Hammer 27c4e94072
refactor: createEntry should return SignedRegistryEntry 2023-12-11 22:55:02 -05:00
semantic-release-bot 838fb98abc chore(release): 0.1.0-develop.9 [skip ci]
# [0.1.0-develop.9](https://git.lumeweb.com/LumeWeb/s5-js/compare/v0.1.0-develop.8...v0.1.0-develop.9) (2023-12-12)

### Features

* add createEntry ([02eefe4](02eefe442c))
* add publishEntry ([5c19245](5c19245b4b))
2023-12-12 03:51:19 +00:00
Derrick Hammer be0f6298c3
Merge remote-tracking branch 'origin/develop' into develop 2023-12-11 22:50:33 -05:00
Derrick Hammer 83143dc960
refactor: return CID object not CID string 2023-12-11 22:49:07 -05:00
Derrick Hammer 02eefe442c
feat: add createEntry 2023-12-11 22:44:43 -05:00
Derrick Hammer 5c19245b4b
feat: add publishEntry 2023-12-11 22:26:42 -05:00
semantic-release-bot 4ed50d4ee7 chore(release): 0.1.0-develop.8 [skip ci]
# [0.1.0-develop.8](https://git.lumeweb.com/LumeWeb/s5-js/compare/v0.1.0-develop.7...v0.1.0-develop.8) (2023-12-12)

### Features

* add uploadWebapp ([28d7c30](28d7c30225))
2023-12-12 02:54:03 +00:00
Derrick Hammer fbffadab8c
Merge remote-tracking branch 'origin/develop' into develop 2023-12-11 21:53:15 -05:00
Derrick Hammer 28d7c30225
feat: add uploadWebapp 2023-12-11 21:53:10 -05:00
semantic-release-bot 24632570e5 chore(release): 0.1.0-develop.7 [skip ci]
# [0.1.0-develop.7](https://git.lumeweb.com/LumeWeb/s5-js/compare/v0.1.0-develop.6...v0.1.0-develop.7) (2023-12-12)
2023-12-12 00:46:15 +00:00
Derrick Hammer 4da2719c26
Merge remote-tracking branch 'origin/develop' into develop 2023-12-11 19:45:25 -05:00
Derrick Hammer 300eb99bfc
dep: update libs5 2023-12-11 19:45:18 -05:00
semantic-release-bot e6951fc9fe chore(release): 0.1.0-develop.6 [skip ci]
# [0.1.0-develop.6](https://git.lumeweb.com/LumeWeb/s5-js/compare/v0.1.0-develop.5...v0.1.0-develop.6) (2023-12-11)

### Features

* add downloadData method ([50ed08a](50ed08ac6a))
2023-12-11 04:34:48 +00:00
Derrick Hammer 31bf33c9a7
Merge remote-tracking branch 'origin/develop' into develop 2023-12-10 23:34:00 -05:00
Derrick Hammer 50ed08ac6a
feat: add downloadData method 2023-12-10 23:33:55 -05:00
semantic-release-bot 19b546c8de chore(release): 0.1.0-develop.5 [skip ci]
# [0.1.0-develop.5](https://git.lumeweb.com/LumeWeb/s5-js/compare/v0.1.0-develop.4...v0.1.0-develop.5) (2023-12-11)

### Bug Fixes

* missing mime dep ([bfce51d](bfce51d1cc))
2023-12-11 03:26:36 +00:00
Derrick Hammer 8b4d189c80
Merge remote-tracking branch 'origin/develop' into develop 2023-12-10 22:25:53 -05:00
Derrick Hammer bfce51d1cc
fix: missing mime dep 2023-12-10 22:25:24 -05:00
semantic-release-bot 99a661c46e chore(release): 0.1.0-develop.4 [skip ci]
# [0.1.0-develop.4](https://git.lumeweb.com/LumeWeb/s5-js/compare/v0.1.0-develop.3...v0.1.0-develop.4) (2023-12-11)

### Bug Fixes

* add main to package.json ([6b17e32](6b17e32cca))
2023-12-11 03:00:31 +00:00
Derrick Hammer 6296c1ef10
dep: update libs5 2023-12-10 21:59:51 -05:00
Derrick Hammer 6b17e32cca
fix: add main to package.json 2023-12-10 21:59:13 -05:00
Derrick Hammer 0dd0197c89
ci: update repository 2023-12-10 21:43:39 -05:00
Derrick Hammer 672b462bbe
feat: add subscribeToEntry 2023-12-10 21:06:43 -05:00
Derrick Hammer 41274582b9
refactor: move upload and download to methods folder 2023-12-10 20:05:57 -05:00
Derrick Hammer 04143ec463
fix: add module to package.json 2023-12-10 16:14:20 -05:00
Derrick Hammer 561b4a0685
ci: fix npm config 2023-12-10 15:32:32 -05:00
Derrick Hammer 395b8a6ca7
fix: import js 2023-12-10 15:29:43 -05:00
Derrick Hammer 80d5d2d027
fix: import js 2023-12-10 15:27:24 -05:00
Derrick Hammer b39b852498
ci: Revert "ci: set allowImportingTsExtensions to false"
This reverts commit 618806c45a.
2023-12-10 15:27:07 -05:00
Derrick Hammer 618806c45a
ci: set allowImportingTsExtensions to false 2023-12-10 15:25:10 -05:00
Derrick Hammer 2752499444
fix: import js files not ts 2023-12-10 15:22:21 -05:00
Derrick Hammer 2a57fb2a7e
ci: set module to nodenext 2023-12-10 15:18:58 -05:00
Derrick Hammer bf8d216f49
ci: setup 2023-12-10 15:17:31 -05:00
Derrick Hammer b3671237b9
feat: initial version 2023-12-10 15:16:45 -05:00
24 changed files with 26678 additions and 1 deletions

11
.changeset/config.json Normal file
View File

@ -0,0 +1,11 @@
{
"$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],
"linked": [],
"access": "public",
"baseBranch": "master",
"updateInternalDependencies": "patch",
"ignore": []
}

13
.presetterrc.json Normal file
View File

@ -0,0 +1,13 @@
{
"preset": [
"@lumeweb/node-library-preset"
],
"config": {
"tsconfig": {
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "node"
}
}
}
}

152
CHANGELOG.md Normal file
View File

@ -0,0 +1,152 @@
# [0.1.0-develop.29](https://git.lumeweb.com/LumeWeb/s5-js/compare/v0.1.0-develop.28...v0.1.0-develop.29) (2024-01-10)
### Bug Fixes
* set binaryType ([d1ce4a1](https://git.lumeweb.com/LumeWeb/s5-js/commit/d1ce4a1ae039dba8ae5497d6106730c44801b83e))
# [0.1.0-develop.28](https://git.lumeweb.com/LumeWeb/s5-js/compare/v0.1.0-develop.27...v0.1.0-develop.28) (2024-01-10)
### Bug Fixes
* use addEventListener, on is not available in all environments ([c4c0a94](https://git.lumeweb.com/LumeWeb/s5-js/commit/c4c0a947a93c60ea2364b7de9b975d6dccc188cc))
# [0.1.0-develop.27](https://git.lumeweb.com/LumeWeb/s5-js/compare/v0.1.0-develop.26...v0.1.0-develop.27) (2023-12-28)
# [0.1.0-develop.26](https://git.lumeweb.com/LumeWeb/s5-js/compare/v0.1.0-develop.25...v0.1.0-develop.26) (2023-12-28)
### Bug Fixes
* use on, once is not available in all environments ([d78ad5e](https://git.lumeweb.com/LumeWeb/s5-js/commit/d78ad5eb2996dc11cc67cf2e4ff1e1b3b62e1519))
# [0.1.0-develop.25](https://git.lumeweb.com/LumeWeb/s5-js/compare/v0.1.0-develop.24...v0.1.0-develop.25) (2023-12-28)
# [0.1.0-develop.24](https://git.lumeweb.com/LumeWeb/s5-js/compare/v0.1.0-develop.23...v0.1.0-develop.24) (2023-12-18)
### Bug Fixes
* ensure we are using a ws protocol ([bb7c7dc](https://git.lumeweb.com/LumeWeb/s5-js/commit/bb7c7dcece26e4ff7de338ca2bf7bc12046edebe))
# [0.1.0-develop.23](https://git.lumeweb.com/LumeWeb/s5-js/compare/v0.1.0-develop.22...v0.1.0-develop.23) (2023-12-17)
### Bug Fixes
* return GetMetadataResponse ([fcdd42f](https://git.lumeweb.com/LumeWeb/s5-js/commit/fcdd42f2a32281ba4dabf4c548657b40b9b4a4ef))
# [0.1.0-develop.22](https://git.lumeweb.com/LumeWeb/s5-js/compare/v0.1.0-develop.21...v0.1.0-develop.22) (2023-12-17)
### Bug Fixes
* need to pass endpointPath ([bf23047](https://git.lumeweb.com/LumeWeb/s5-js/commit/bf230478b792860ce16b68b976853d9a7d828d0f))
# [0.1.0-develop.21](https://git.lumeweb.com/LumeWeb/s5-js/compare/v0.1.0-develop.20...v0.1.0-develop.21) (2023-12-17)
### Bug Fixes
* on existing entry, need to update the entry data ([389a15e](https://git.lumeweb.com/LumeWeb/s5-js/commit/389a15eef41c941e36d2b08ca47c775f13bbba55))
# [0.1.0-develop.20](https://git.lumeweb.com/LumeWeb/s5-js/compare/v0.1.0-develop.19...v0.1.0-develop.20) (2023-12-12)
### Bug Fixes
* ensure we prefix the CID ed22519 type to the registry pubkey ([e4dc098](https://git.lumeweb.com/LumeWeb/s5-js/commit/e4dc0985471747404d82b434f9b8edc97bf0ee1b))
# [0.1.0-develop.19](https://git.lumeweb.com/LumeWeb/s5-js/compare/v0.1.0-develop.18...v0.1.0-develop.19) (2023-12-12)
# [0.1.0-develop.18](https://git.lumeweb.com/LumeWeb/s5-js/compare/v0.1.0-develop.17...v0.1.0-develop.18) (2023-12-12)
### Bug Fixes
* need to pass as query, not data ([820cc5b](https://git.lumeweb.com/LumeWeb/s5-js/commit/820cc5bd8924391305ab37e20df53fb9f68924f5))
# [0.1.0-develop.17](https://git.lumeweb.com/LumeWeb/s5-js/compare/v0.1.0-develop.16...v0.1.0-develop.17) (2023-12-12)
### Bug Fixes
* use get not post ([e24d352](https://git.lumeweb.com/LumeWeb/s5-js/commit/e24d352d4eb6fd5baf16698f11c876e79a3040fd))
# [0.1.0-develop.16](https://git.lumeweb.com/LumeWeb/s5-js/compare/v0.1.0-develop.15...v0.1.0-develop.16) (2023-12-12)
### Features
* add getEntry and update createEntry to use it ([aea2360](https://git.lumeweb.com/LumeWeb/s5-js/commit/aea236067e6011502f6df2ec9856597b9fe5b1a9))
# [0.1.0-develop.15](https://git.lumeweb.com/LumeWeb/s5-js/compare/v0.1.0-develop.14...v0.1.0-develop.15) (2023-12-12)
### Bug Fixes
* need to use toRegistryEntry ([eb3f2ee](https://git.lumeweb.com/LumeWeb/s5-js/commit/eb3f2ee5c9da7b3b46a5d3d61c5dfed8ecfc167c))
# [0.1.0-develop.14](https://git.lumeweb.com/LumeWeb/s5-js/compare/v0.1.0-develop.13...v0.1.0-develop.14) (2023-12-12)
### Bug Fixes
* need to strip off multiformats prefix ([502c8fb](https://git.lumeweb.com/LumeWeb/s5-js/commit/502c8fb0795986b0a488ad3684c1c23f0b807a40))
# [0.1.0-develop.13](https://git.lumeweb.com/LumeWeb/s5-js/compare/v0.1.0-develop.12...v0.1.0-develop.13) (2023-12-12)
### Bug Fixes
* Revert "fix: use base64urlpad" ([74407cf](https://git.lumeweb.com/LumeWeb/s5-js/commit/74407cf9a15c5c846592f3929646cdcbba98f032))
# [0.1.0-develop.12](https://git.lumeweb.com/LumeWeb/s5-js/compare/v0.1.0-develop.11...v0.1.0-develop.12) (2023-12-12)
### Bug Fixes
* use base64urlpad ([ce47696](https://git.lumeweb.com/LumeWeb/s5-js/commit/ce47696d907513e6b5a78f3c71407e12b90fb952))
# [0.1.0-develop.11](https://git.lumeweb.com/LumeWeb/s5-js/compare/v0.1.0-develop.10...v0.1.0-develop.11) (2023-12-12)
# [0.1.0-develop.10](https://git.lumeweb.com/LumeWeb/s5-js/compare/v0.1.0-develop.9...v0.1.0-develop.10) (2023-12-12)
# [0.1.0-develop.9](https://git.lumeweb.com/LumeWeb/s5-js/compare/v0.1.0-develop.8...v0.1.0-develop.9) (2023-12-12)
### Features
* add createEntry ([02eefe4](https://git.lumeweb.com/LumeWeb/s5-js/commit/02eefe442cc8694898b0989dd8f0b6d15fb32b0e))
* add publishEntry ([5c19245](https://git.lumeweb.com/LumeWeb/s5-js/commit/5c19245b4bfb75ece2c7ffa3153fbf49bc70e602))
# [0.1.0-develop.8](https://git.lumeweb.com/LumeWeb/s5-js/compare/v0.1.0-develop.7...v0.1.0-develop.8) (2023-12-12)
### Features
* add uploadWebapp ([28d7c30](https://git.lumeweb.com/LumeWeb/s5-js/commit/28d7c30225c400cf87fc6e6e2d7eda6061acb025))
# [0.1.0-develop.7](https://git.lumeweb.com/LumeWeb/s5-js/compare/v0.1.0-develop.6...v0.1.0-develop.7) (2023-12-12)
# [0.1.0-develop.6](https://git.lumeweb.com/LumeWeb/s5-js/compare/v0.1.0-develop.5...v0.1.0-develop.6) (2023-12-11)
### Features
* add downloadData method ([50ed08a](https://git.lumeweb.com/LumeWeb/s5-js/commit/50ed08ac6a23f9fa7e43450c0f8942124fa175cd))
# [0.1.0-develop.5](https://git.lumeweb.com/LumeWeb/s5-js/compare/v0.1.0-develop.4...v0.1.0-develop.5) (2023-12-11)
### Bug Fixes
* missing mime dep ([bfce51d](https://git.lumeweb.com/LumeWeb/s5-js/commit/bfce51d1cc704fb4dc6752fc7fc878db97266d6b))
# [0.1.0-develop.4](https://git.lumeweb.com/LumeWeb/s5-js/compare/v0.1.0-develop.3...v0.1.0-develop.4) (2023-12-11)
### Bug Fixes
* add main to package.json ([6b17e32](https://git.lumeweb.com/LumeWeb/s5-js/commit/6b17e32cca10733fab1f04aecb06615c19a3e694))

View File

@ -1,6 +1,8 @@
MIT License MIT License
Copyright (c) 2023 LumeWeb Copyright (c) 2023 Hammer Technologies LLC
Copyright (c) 2022 parajbs
Copyright (c) 2020 Nebulous
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

23986
npm-shrinkwrap.json generated Normal file

File diff suppressed because it is too large Load Diff

18
orval.config.ts Normal file
View File

@ -0,0 +1,18 @@
import { defineConfig } from "orval";
export default defineConfig({
s5: {
input: "./swagger.yaml",
output: {
mode: "split",
workspace: "./src/generated",
target: "openapi.ts",
override: {
mutator: {
path: "../axios.ts",
name: "customInstance",
},
},
},
},
});

41
package.json Normal file
View File

@ -0,0 +1,41 @@
{
"name": "@lumeweb/s5-js",
"version": "0.1.0-develop.29",
"type": "module",
"module": "lib/index.js",
"main": "lib/index.js",
"repository": {
"type": "git",
"url": "gitea@git.lumeweb.com:LumeWeb/s5-js.git"
},
"devDependencies": {
"@changesets/cli": "^2.27.1",
"@lumeweb/node-library-preset": "^0.2.7",
"@types/ws": "^8.5.10",
"orval": "^6.24.0",
"presetter": "*"
},
"readme": "ERROR: No README data found!",
"scripts": {
"prepare": "presetter bootstrap",
"build": "run build",
"patch-package": "patch-package",
"postinstall": "patch-package"
},
"dependencies": {
"@lumeweb/libs5": "0.0.0-20240321165322",
"@noble/hashes": "^1.3.2",
"axios": "^1.6.2",
"isomorphic-ws": "^5.0.0",
"mime": "^4.0.1",
"multiformats": "^13.0.0",
"p-defer": "^4.0.0",
"patch-package": "^8.0.0",
"tus-js-client": "^4.0.0",
"url-join": "^5.0.0",
"url-parse": "^1.5.10"
},
"publishConfig": {
"access": "public"
}
}

View File

@ -0,0 +1,15 @@
diff --git a/node_modules/tsc-alias/dist/utils/import-path-resolver.js b/node_modules/tsc-alias/dist/utils/import-path-resolver.js
index ebaf620..227d4c7 100644
--- a/node_modules/tsc-alias/dist/utils/import-path-resolver.js
+++ b/node_modules/tsc-alias/dist/utils/import-path-resolver.js
@@ -5,8 +5,9 @@ const normalizePath = require("normalize-path");
const fs_1 = require("fs");
const path_1 = require("path");
const anyQuote = `["']`;
+const excludeLocalImportSyntax = `[(?!\.)]`;
const pathStringContent = `[^"'\r\n]+`;
-const importString = `(?:${anyQuote}${pathStringContent}${anyQuote})`;
+const importString = `(?:${anyQuote}${excludeLocalImportSyntax}${pathStringContent}${anyQuote})`;
const funcStyle = `(?:\\b(?:import|require)\\s*\\(\\s*(\\/\\*.*\\*\\/\\s*)?${importString}\\s*\\))`;
const globalStyle = `(?:\\bimport\\s+${importString})`;
const fromStyle = `(?:\\bfrom\\s+${importString})`;

46
src/axios.ts Normal file
View File

@ -0,0 +1,46 @@
import Axios, { AxiosError, AxiosRequestConfig } from "axios";
import { S5Error } from "./client.js";
export interface CancelablePromise<T> extends Promise<T> {
cancel: () => void;
}
export const customInstance = <T>(
config: AxiosRequestConfig,
options?: AxiosRequestConfig,
): CancelablePromise<T> => {
const abort = new AbortController();
/*
Hack to ensure that the data is passed to the request as an option.
*/
if (options?.data) {
config = config || {};
config.data = options.data;
delete config.data;
}
const instance = Axios.create({ baseURL: options?.baseURL });
const promise = instance({
signal: abort.signal,
...config,
...options,
})
.then(({ data }) => data)
.catch((error) => {
if (Axios.isCancel(error)) {
return;
}
throw new S5Error(
(error as AxiosError).message,
(error as AxiosError).response?.status as number,
);
});
// @ts-ignore
promise.cancel = () => {
abort.abort("Query was cancelled");
};
return promise as CancelablePromise<T>;
};

759
src/client.ts Normal file
View File

@ -0,0 +1,759 @@
import {
CustomDownloadOptions,
CustomGetMetadataOptions,
DEFAULT_DOWNLOAD_OPTIONS,
DEFAULT_GET_METADATA_OPTIONS,
MetadataResult,
} from "./options/download.js";
import {addUrlQuery, ensureUrl} from "./utils/url.js";
import {
CustomRegistryOptions,
DEFAULT_GET_ENTRY_OPTIONS,
DEFAULT_PUBLISH_ENTRY_OPTIONS,
DEFAULT_SUBSCRIBE_ENTRY_OPTIONS,
} from "./options/registry.js";
import {CustomClientOptions, optionsToConfig} from "./utils/options.js";
import {throwValidationError} from "./utils/validation.js";
import {
AccountPinsResponse,
BasicUploadResponse, deleteS5DeleteCid,
getS5AccountPins,
getS5BlobCid,
getS5DownloadCid,
getS5MetadataCid, getS5PinCidStatus,
getS5Registry, postS5PinCid,
postS5Registry,
postS5Upload,
postS5UploadDirectory,
PostS5UploadDirectoryParams,
PostS5UploadResult,
} from "./generated/index.js";
import path from "path";
import {customInstance} from "./axios.js";
import {ensureBytes, equalBytes} from "@noble/curves/abstract/utils";
import {concatBytes} from "@noble/hashes/utils";
import {CID_HASH_TYPES} from "@lumeweb/libs5";
import {buildRequestUrl} from "./request.js";
import WS from "isomorphic-ws";
import {
CID,
CID_TYPES,
createKeyPair,
KeyPairEd25519,
Packer,
SignedRegistryEntry,
} from "@lumeweb/libs5";
import {
deserializeRegistryEntry,
signRegistryEntry,
verifyRegistryEntry,
} from "@lumeweb/libs5/lib/service/registry.js";
import {Buffer} from "buffer";
import {AxiosError} from "axios";
import {
CustomUploadOptions,
DEFAULT_UPLOAD_OPTIONS,
TUS_ENDPOINT,
UploadResult,
} from "./options/upload.js";
import {
DetailedError,
HttpRequest,
Upload,
UploadOptions,
} from "tus-js-client";
import {ensureFileObjectConsistency} from "./utils/file.js";
import defer from "p-defer";
import {Multihash} from "@lumeweb/libs5/lib/multihash.js";
import {blake3} from "@noble/hashes/blake3";
import {base64urlDecode, base64urlEncode} from "./utils/encoding.js";
import {CustomPinOptions, DEFAULT_PIN_OPTIONS} from "./options/pin.js";
export class S5Error extends Error {
public statusCode: number;
constructor(message: string, statusCode: number) {
super(message);
this.name = "S5Error";
this.statusCode = statusCode;
}
}
/**
* The S5 Client which can be used to access S5-net.
*/
export class S5Client {
/**
* The S5 Client which can be used to access S5-net.
*
* @class
* @param [portalUrl] The initial portal URL to use to access S5, if specified. A request will be made to this URL to get the actual portal URL. To use the default portal while passing custom options, pass "".
* @param [customOptions] Configuration for the client.
*/
constructor(portalUrl: string, customOptions: CustomClientOptions = {}) {
if (!portalUrl) {
throwValidationError("portalUrl", portalUrl, "parameter", "string");
}
this._portalUrl = ensureUrl(portalUrl);
this._clientOptions = customOptions;
}
private _clientOptions: CustomClientOptions;
get clientOptions(): CustomClientOptions {
return this._clientOptions;
}
set clientOptions(value: CustomClientOptions) {
this._clientOptions = value;
}
private _portalUrl: string;
get portalUrl(): string {
return this._portalUrl;
}
public static create(
portalUrl: string,
customOptions: CustomClientOptions = {},
) {
return new S5Client(portalUrl, customOptions);
}
public async accountPins(
customOptions: CustomClientOptions = {},
): Promise<AccountPinsResponse> {
const opts = {
...this.clientOptions,
...customOptions,
...{
endpointPath: "/s5/account/pins",
baseUrl: await this.portalUrl,
},
};
const config = optionsToConfig(this, opts);
return await getS5AccountPins(config);
}
/**
* Initiates a download of the content of the cid within the browser.
*
* @param cid - 46-character cid, or a valid cid URL. Can be followed by a path. Note that the cid will not be encoded, so if your path might contain special characters, consider using `clientOptions.path`.
* @param [customOptions] - Additional settings that can optionally be set.
* @param [customOptions.endpointDownload="/"] - The relative URL path of the portal endpoint to contact.
* @returns - The full URL that was used.
* @throws - Will throw if the cid does not contain a cid or if the path option is not a string.
*/
public async downloadFile(
cid: string,
customOptions?: CustomDownloadOptions,
): Promise<string> {
const url = await this.getCidUrl(cid, customOptions);
// Download the url.
window.location.assign(url);
return url;
}
/**
* Constructs the full URL for the given cid.
*
* @param cid - Base64 cid, or a valid URL that contains a cid. See `downloadFile`.
* @param [customOptions] - Additional settings that can optionally be set.
* @param [customOptions.endpointDownload="/"] - The relative URL path of the portal endpoint to contact.
* @returns - The full URL for the cid.
* @throws - Will throw if the cid does not contain a cid or if the path option is not a string.
*/
public async getCidUrl(
cid: string,
customOptions: CustomDownloadOptions = {},
): Promise<string> {
const opt = {...this.clientOptions, customOptions};
return addUrlQuery(path.join(this.portalUrl, cid), {
auth_token: opt.apiKey,
});
}
/**
* Gets only the metadata for the given cid without the contents.
*
* @param cid - Base64 cid.
* @param [customOptions] - Additional settings that can optionally be set. See `downloadFile` for the full list.
* @param [customOptions.endpointGetMetadata="/"] - The relative URL path of the portal endpoint to contact.
* @returns - The metadata in JSON format. Empty if no metadata was found.
* @throws - Will throw if the cid does not contain a cid .
*/
public async getMetadata(
cid: string,
customOptions: CustomGetMetadataOptions = {},
): Promise<MetadataResult> {
const config = optionsToConfig(
this,
DEFAULT_GET_METADATA_OPTIONS,
customOptions,
);
const response = await getS5MetadataCid(cid, config);
return {metadata: response};
}
/**
* Downloads in-memory data from a S5 cid.
* @param cid - 46-character cid, or a valid cid URL.
* @param [customOptions] - Additional settings that can optionally be set.
* @returns - The data
*/
public async downloadData(
cid: string,
customOptions: CustomDownloadOptions = {},
): Promise<ArrayBuffer> {
const config = optionsToConfig(
this,
DEFAULT_DOWNLOAD_OPTIONS,
customOptions,
);
return await (await getS5DownloadCid(cid, config)).arrayBuffer();
}
/**
* Downloads a proof for the given cid.
* @param cid - 46-character cid, or a valid cid URL.
* @param [customOptions] - Additional settings that can optionally be set.
* @returns - The data
*/
public async downloadProof(
cid: string,
customOptions: CustomDownloadOptions = {},
): Promise<ArrayBuffer> {
return this.downloadData(`${cid}.obao`, customOptions);
}
/**
* Downloads a blob from the given cid. This will capture a 301 redirect to the actual blob location, then download the blob.
* @param cid - 46-character cid, or a valid cid URL.
* @param [customOptions] - Additional settings that can optionally be set.
* @returns - The data
*/
async downloadBlob(
cid: string,
customOptions: CustomDownloadOptions = {},
): Promise<ArrayBuffer> {
const config = optionsToConfig(
this,
DEFAULT_DOWNLOAD_OPTIONS,
customOptions,
);
let location: string | null = null;
await getS5BlobCid(cid, {
...config,
responseType: "arraybuffer",
beforeRedirect: (config, responseDetails) => {
location = responseDetails.headers["location"];
},
});
if (!location) {
throw new Error("Failed to download blob");
}
return await customInstance<ArrayBuffer>(
{
url: `/s5/blob/${cid}`,
method: "GET",
responseType: "arraybuffer",
},
config,
);
}
public async subscribeToEntry(
publicKey: Uint8Array,
customOptions: CustomRegistryOptions = {},
) {
const opts = {
...DEFAULT_SUBSCRIBE_ENTRY_OPTIONS,
...this.clientOptions,
...customOptions,
} satisfies CustomRegistryOptions;
publicKey = ensureBytes("public key", publicKey, 32);
publicKey = concatBytes(
Uint8Array.from([CID_HASH_TYPES.ED25519]),
publicKey,
);
const url = await buildRequestUrl(this, {
baseUrl: await this.portalUrl,
endpointPath: opts.endpointSubscribeEntry,
});
const wsUrl = url.replace(/^http/, "ws");
const socket = new WS(wsUrl);
socket.binaryType = "arraybuffer";
socket.addEventListener("open", () => {
const packer = new Packer();
packer.pack(2);
packer.pack(publicKey);
socket.send(packer.takeBytes());
});
return {
listen(cb: (entry: SignedRegistryEntry) => void) {
socket.addEventListener("message", (data) => {
cb(deserializeRegistryEntry(new Uint8Array(data.data as Buffer)));
});
},
end() {
if (
[socket.CLOSING, socket.CLOSED].includes(socket.readyState as any)
) {
return;
}
socket.close();
},
};
}
public async publishEntry(
signedEntry: SignedRegistryEntry,
customOptions: CustomRegistryOptions = {},
) {
const config = optionsToConfig(
this,
DEFAULT_PUBLISH_ENTRY_OPTIONS,
customOptions,
);
if (!verifyRegistryEntry(signedEntry)) {
throwValidationError(
"signedEntry", // name of the variable
signedEntry, // actual value
"parameter", // valueKind (assuming it's a function parameter)
"a valid signed registry entry", // expected description
);
}
return postS5Registry(
{
pk: base64urlEncode(signedEntry.pk),
revision: signedEntry.revision,
data: base64urlEncode(signedEntry.data),
signature: base64urlEncode(signedEntry.signature),
},
config,
);
}
public async createEntry(
sk: Uint8Array | KeyPairEd25519,
cid: CID,
revision = 0,
) {
if (sk instanceof Uint8Array) {
sk = createKeyPair(sk);
}
let existing = true;
let entry = await this.getEntry(sk.publicKey);
if (!entry) {
existing = false;
entry = {
pk: sk.publicKey,
data: cid.toRegistryEntry(),
revision,
} as unknown as SignedRegistryEntry;
}
if (!equalBytes(sk.publicKey, entry.pk)) {
throwValidationError(
"entry.pk", // name of the variable
Buffer.from(entry.pk).toString("hex"), // actual value
"result", // valueKind (assuming it's a function parameter)
Buffer.from(sk.publicKey).toString("hex"), // expected description
);
}
if (existing) {
const newEntry = cid.toRegistryEntry();
if (equalBytes(entry.data, newEntry)) {
return entry;
}
entry.revision++;
entry.data = newEntry;
}
const signedEntry = signRegistryEntry({
kp: sk,
data: entry.data,
revision: entry.revision,
});
await this.publishEntry(signedEntry);
return signedEntry;
}
public async getEntry(
publicKey: Uint8Array,
customOptions: CustomRegistryOptions = {},
) {
const config = optionsToConfig(
this,
DEFAULT_GET_ENTRY_OPTIONS,
customOptions,
);
try {
const ret = await getS5Registry(
{
pk: base64urlEncode(publicKey),
},
config,
);
const signedEntry = {
pk: base64urlDecode(<string>ret.pk),
revision: ret.revision,
data: base64urlDecode(<string>ret.data),
signature: base64urlDecode(<string>ret.signature),
} as SignedRegistryEntry;
if (!verifyRegistryEntry(signedEntry)) {
throwValidationError(
"signedEntry", // name of the variable
signedEntry, // actual value
"result", // valueKind (assuming it's a function parameter)
"a valid signed registry entry", // expected description
);
}
return signedEntry;
} catch (e) {
if ((e as AxiosError).response?.status === 404) {
return undefined;
}
throw e;
}
}
/**
* Uploads a file to S5-net.
*
* @param file - The file to upload.
* @param [customOptions] - Additional settings that can optionally be set.
* @returns - The returned cid.
* @throws - Will throw if the request is successful but the upload response does not contain a complete response.
*/
public async uploadFile(
file: File,
customOptions: CustomUploadOptions = {},
): Promise<any> {
const opts = {
...DEFAULT_UPLOAD_OPTIONS,
...this.clientOptions,
...customOptions,
} as CustomUploadOptions;
if (file.size < <number>opts?.largeFileSize) {
return this.uploadSmallFile(file, opts);
} else {
return this.uploadLargeFile(file, opts);
}
}
/**
* Uploads a small file to S5-net.
*
* @param file - The file to upload.
* @param [customOptions] - Additional settings that can optionally be set.
* @returns UploadResult - The returned cid.
* @throws - Will throw if the request is successful but the upload response does not contain a complete response.
*/
public async uploadSmallFile(
file: File,
customOptions: CustomUploadOptions,
): Promise<UploadResult> {
const response = await this.uploadSmallFileRequest(file, customOptions);
return {cid: CID.decode(<string>response.cid)};
}
/* istanbul ignore next */
/**
* Uploads a large file to S5-net using tus.
*
* @param file - The file to upload.
* @param [customOptions] - Additional settings that can optionally be set.
* @param [customOptions.endpointLargeUpload="/s5/upload/tus"] - The relative URL path of the portal endpoint to contact.
* @returns - The returned cid.
* @throws - Will throw if the request is successful but the upload response does not contain a complete response.
*/
public async uploadLargeFile(
file: File,
customOptions: CustomUploadOptions = {},
): Promise<UploadResult> {
return await this.uploadLargeFileRequest(file, customOptions);
}
public async getTusOptions(
file: File,
tusOptions: Partial<UploadOptions> = {},
customOptions: CustomUploadOptions = {},
): Promise<UploadOptions> {
const config = optionsToConfig(this, DEFAULT_UPLOAD_OPTIONS, customOptions);
// Validation.
const url = await buildRequestUrl(this, {
endpointPath: TUS_ENDPOINT,
});
file = ensureFileObjectConsistency(file);
const hasher = blake3.create({});
const chunkSize = 1024 * 1024;
let position = 0;
while (position <= file.size) {
const chunk = file.slice(position, position + chunkSize);
``;
hasher.update(new Uint8Array(await chunk.arrayBuffer()));
position += chunkSize;
customOptions.onHashProgress?.({
bytes: position,
total: file.size,
});
}
const b3hash = hasher.digest();
const filename = new Multihash(
Buffer.concat([
Buffer.alloc(1, CID_HASH_TYPES.BLAKE3),
Buffer.from(b3hash),
]),
).toBase64Url();
return {
endpoint: url,
metadata: {
hash: filename,
filename: filename,
filetype: file.type,
},
headers: config.headers as any,
onBeforeRequest: function (req: HttpRequest) {
const xhr = req.getUnderlyingObject();
xhr.withCredentials = true;
},
...tusOptions,
};
}
/**
* Uploads a directory to S5-net.
*
* @param directory - File objects to upload, indexed by their path strings.
* @param filename - The name of the directory.
* @param [customOptions] - Additional settings that can optionally be set.
* @param [customOptions.endpointPath="/s5/upload/directory"] - The relative URL path of the portal endpoint to contact.
* @returns - The returned cid.
* @throws - Will throw if the request is successful but the upload response does not contain a complete response.
*/
public async uploadDirectory(
directory: Record<string, File>,
filename: string,
customOptions: CustomUploadOptions = {},
): Promise<UploadResult> {
const response = await this.uploadDirectoryRequest(
directory,
filename,
customOptions,
);
return {cid: CID.decode(<string>response.cid)};
}
/**
* Makes a request to upload a directory to S5-net.
*
* @param directory - File objects to upload, indexed by their path strings.
* @param filename - The name of the directory.
* @param [customOptions] - Additional settings that can optionally be set.
* @returns - The upload response.
* @throws - Will throw if the input filename is not a string.
*/
public async uploadDirectoryRequest(
directory: Record<string, File>,
filename: string,
customOptions: CustomUploadOptions = {},
): Promise<BasicUploadResponse> {
const config = optionsToConfig(this, DEFAULT_UPLOAD_OPTIONS, customOptions);
const formData = new FormData();
for (const entry in directory) {
const file = ensureFileObjectConsistency(directory[entry]);
formData.append(entry, file, entry);
}
const params = {} as PostS5UploadDirectoryParams;
if (customOptions.tryFiles) {
params.tryFiles = customOptions.tryFiles;
}
if (customOptions.errorPages) {
params.errorPages = customOptions.errorPages;
}
params.name = filename;
/*
Hack to pass the data right since OpenAPI doesn't support variable file inputs without knowing the names ahead of time.
*/
config.data = formData;
return postS5UploadDirectory({}, params, config);
}
public async uploadWebapp(
directory: Record<string, File>,
customOptions: CustomUploadOptions = {},
): Promise<UploadResult> {
const response = await this.uploadWebappRequest(directory, customOptions);
return {cid: CID.decode(<string>response.cid)};
}
/**
* Makes a request to upload a directory to S5-net.
* @param directory - File objects to upload, indexed by their path strings.
* @param [customOptions] - Additional settings that can optionally be set.
* @param [customOptions.endpointPath] - The relative URL path of the portal endpoint to contact.
* @returns - The upload response.
* @throws - Will throw if the input filename is not a string.
*/
public async uploadWebappRequest(
directory: Record<string, File>,
customOptions: CustomUploadOptions = {},
): Promise<BasicUploadResponse> {
return this.uploadDirectoryRequest(directory, "webapp", customOptions);
}
/**
* Makes a request to upload a small file to S5-net.
*
* @param file - The file to upload.
* @param [customOptions] - Additional settings that can optionally be set.
* @returns PostS5UploadResult - The upload response.
*/
private async uploadSmallFileRequest(
file: File,
customOptions: CustomUploadOptions = {},
): Promise<PostS5UploadResult> {
const config = optionsToConfig(this, DEFAULT_UPLOAD_OPTIONS, customOptions);
file = ensureFileObjectConsistency(file);
return postS5Upload(
file,
config,
);
}
/* istanbul ignore next */
/**
* Makes a request to upload a file to S5-net.
*
* @param file - The file to upload.
* @param [customOptions] - Additional settings that can optionally be set.
* @returns - The upload response.
*/
private async uploadLargeFileRequest(
file: File,
customOptions: CustomUploadOptions = {},
): Promise<UploadResult> {
const p = defer<UploadResult>();
const options = await this.getTusOptions(
file,
{
onSuccess: async () => {
if (!upload.url) {
p.reject(new Error("'upload.url' was not set"));
return;
}
p.resolve({cid});
},
onError: (error: Error | DetailedError) => {
// Return error body rather than entire error.
const res = (error as DetailedError).originalResponse;
const newError = res
? new Error(res.getBody().trim()) || error
: error;
p.reject(newError);
},
},
customOptions,
);
const cid = CID.fromHash(
Multihash.fromBase64Url(<string>options.metadata?.hash).fullBytes,
file.size,
CID_TYPES.RAW,
);
const upload = new Upload(file, options);
return p.promise;
}
public async pin(cid: string, customOptions: CustomPinOptions = {}) {
const config = optionsToConfig(
this,
DEFAULT_PIN_OPTIONS,
customOptions,
);
await postS5PinCid(cid, config);
}
public async unpin(cid: string, customOptions: CustomPinOptions = {}) {
const config = optionsToConfig(
this,
DEFAULT_PIN_OPTIONS,
customOptions,
);
await deleteS5DeleteCid(cid, config);
}
public async pinStatus(cid: string, customOptions: CustomPinOptions = {}) {
const config = optionsToConfig(
this,
DEFAULT_PIN_OPTIONS,
customOptions,
);
return await getS5PinCidStatus(cid, config);
}
}

3
src/index.ts Normal file
View File

@ -0,0 +1,3 @@
export { S5Client } from "./client.js";
export type { CustomClientOptions } from "./utils/options.js";
export type { HashProgressEvent } from "./options/upload.js";

21
src/number.ts Normal file
View File

@ -0,0 +1,21 @@
import { Buffer } from "buffer";
/**
* convert a number to Buffer.
*
* @param value - File objects to upload, indexed by their path strings.
* @returns - The returned cid.
* @throws - Will throw if the request is successful but the upload response does not contain a complete response.
*/
function numberToBuffer(value: number) {
const view = Buffer.alloc(16);
let lastIndex = 15;
for (let index = 0; index <= 15; ++index) {
if (value % 256 !== 0) {
lastIndex = index;
}
view[index] = value % 256;
value = value >> 8;
}
return view.subarray(0, lastIndex + 1);
}

28
src/options/download.ts Normal file
View File

@ -0,0 +1,28 @@
import { ResponseType } from "axios";
import { CustomClientOptions } from "../utils/options.js";
export type CustomDownloadOptions = CustomClientOptions & {
path?: string;
range?: string;
responseType?: ResponseType;
};
export type CustomGetMetadataOptions = CustomClientOptions & {};
/**
* The response for a get metadata request.
*
* @property metadata - The metadata in JSON format.
* @property portalUrl - The URL of the portal.
* @property cid - 46-character cid.
*/
export type MetadataResult = {
metadata: Record<string, unknown>;
};
export const DEFAULT_DOWNLOAD_OPTIONS = {
range: undefined,
responseType: undefined,
} as CustomDownloadOptions;
export const DEFAULT_GET_METADATA_OPTIONS = {};

8
src/options/pin.ts Normal file
View File

@ -0,0 +1,8 @@
import {CustomClientOptions} from "#utils/options.js";
import {ResponseType} from "axios";
export type CustomPinOptions = CustomClientOptions & {
};
export const DEFAULT_PIN_OPTIONS = {};

22
src/options/registry.ts Normal file
View File

@ -0,0 +1,22 @@
import { CustomClientOptions } from "../utils/options.js";
export const DEFAULT_GET_ENTRY_OPTIONS = {};
export const DEFAULT_SET_ENTRY_OPTIONS = {
endpointSetEntry: "/s5/registry",
};
export const DEFAULT_SUBSCRIBE_ENTRY_OPTIONS = {
endpointSubscribeEntry: "/s5/registry/subscription",
} satisfies CustomRegistryOptions;
export const DEFAULT_PUBLISH_ENTRY_OPTIONS = {
endpointPublishEntry: "/s5/registry",
} satisfies CustomRegistryOptions;
export type BaseCustomOptions = CustomClientOptions;
export interface CustomRegistryOptions extends BaseCustomOptions {
endpointSubscribeEntry?: string;
endpointPublishEntry?: string;
}

81
src/options/upload.ts Normal file
View File

@ -0,0 +1,81 @@
import { AxiosProgressEvent } from "axios";
import {
DetailedError,
HttpRequest,
Upload,
UploadOptions,
} from "tus-js-client";
import { blake3 } from "@noble/hashes/blake3";
import { Buffer } from "buffer";
import { getFileMimeType } from "../utils/file.js";
import { S5Client } from "../client.js";
import { CID, CID_HASH_TYPES, CID_TYPES } from "@lumeweb/libs5";
import {
type BasicUploadResponse,
postS5Upload,
postS5UploadDirectory,
PostS5UploadDirectoryParams,
PostS5UploadResult,
} from "../generated/index.js";
import { BaseCustomOptions } from "./registry.js";
import { optionsToConfig } from "../utils/options.js";
import { buildRequestUrl } from "../request.js";
import defer from "p-defer";
import { Multihash } from "@lumeweb/libs5/lib/multihash.js";
/**
* The tus chunk size is (4MiB - encryptionOverhead) * dataPieces, set as default.
*/
export const TUS_CHUNK_SIZE = (1 << 22) * 8;
/**
* The retry delays, in ms. Data is stored in for up to 20 minutes, so the
* total delays should not exceed that length of time.
*/
const DEFAULT_TUS_RETRY_DELAYS = [0, 5_000, 15_000, 60_000, 300_000, 600_000];
/**
* The portal file field name.
*/
const PORTAL_FILE_FIELD_NAME = "file";
export const TUS_ENDPOINT = "/s5/upload/tus";
export interface HashProgressEvent {
bytes: number;
total: number;
}
/**
* Custom upload options.
*
* @property [largeFileSize=32943040] - The size at which files are considered "large" and will be uploaded using the tus resumable upload protocol. This is the size of one chunk by default (32 mib). Note that this does not affect the actual size of chunks used by the protocol.
* @property [errorPages] - Defines a mapping of error codes and subfiles which are to be served in case we are serving the respective error code. All subfiles referred like this must be defined with absolute paths and must exist.
* @property [retryDelays=[0, 5_000, 15_000, 60_000, 300_000, 600_000]] - An array or undefined, indicating how many milliseconds should pass before the next attempt to uploading will be started after the transfer has been interrupted. The array's length indicates the maximum number of attempts.
* @property [tryFiles] - Allows us to set a list of potential subfiles to return in case the requested one does not exist or is a directory. Those subfiles might be listed with relative or absolute paths. If the path is absolute the file must exist.
*/
export type CustomUploadOptions = BaseCustomOptions & {
errorPages?: Record<string, string>;
tryFiles?: string[];
// Large files.
largeFileSize?: number;
retryDelays?: number[];
onUploadProgress?: (progressEvent: AxiosProgressEvent) => void;
onHashProgress?: (progressEvent: HashProgressEvent) => void;
};
export const DEFAULT_UPLOAD_OPTIONS = {
errorPages: { 404: "/404.html" },
tryFiles: ["index.html"],
// Large files.
largeFileSize: TUS_CHUNK_SIZE,
retryDelays: DEFAULT_TUS_RETRY_DELAYS,
} as CustomUploadOptions;
export interface UploadResult {
cid: CID;
}

62
src/request.ts Normal file
View File

@ -0,0 +1,62 @@
import { AxiosError, AxiosResponse, InternalAxiosRequestConfig } from "axios";
import { S5Client } from "./client.js";
import {
addUrlQuery,
addUrlSubdomain,
ensureUrlPrefix,
makeUrl,
} from "./utils/url.js";
export type Headers = { [key: string]: string };
/**
* Helper function that builds the request URL. Ensures that the final URL
* always has a protocol prefix for consistency.
*
* @param client - The S5 client.
* @param parts - The URL parts to use when constructing the URL.
* @param [parts.baseUrl] - The base URL to use, instead of the portal URL.
* @param [parts.endpointPath] - The endpoint to contact.
* @param [parts.subdomain] - An optional subdomain to add to the URL.
* @param [parts.extraPath] - An optional path to append to the URL.
* @param [parts.query] - Optional query parameters to append to the URL.
* @returns - The built URL.
*/
export async function buildRequestUrl(
client: S5Client,
parts: {
baseUrl?: string;
endpointPath?: string;
subdomain?: string;
extraPath?: string;
query?: { [key: string]: string | undefined };
},
): Promise<string> {
let url;
// Get the base URL, if not passed in.
if (!parts.baseUrl) {
url = await client.portalUrl;
} else {
url = parts.baseUrl;
}
// Make sure the URL has a protocol.
url = ensureUrlPrefix(url);
if (parts.endpointPath) {
url = makeUrl(url, parts.endpointPath);
}
if (parts.extraPath) {
url = makeUrl(url, parts.extraPath);
}
if (parts.subdomain) {
url = addUrlSubdomain(url, parts.subdomain);
}
if (parts.query) {
url = addUrlQuery(url, parts.query);
}
return url;
}

5
src/utils/encoding.ts Normal file
View File

@ -0,0 +1,5 @@
import { base64url } from "multiformats/bases/base64";
export const base64urlEncode = (d: Uint8Array) =>
base64url.encode(d).substring(1);
export const base64urlDecode = (d: string) => base64url.decode(`u${d}`);

38
src/utils/file.ts Normal file
View File

@ -0,0 +1,38 @@
import mime from "mime";
import path from "path";
import { trimPrefix } from "./string.js";
/**
* Get the file mime type. In case the type is not provided, try to guess the
* file type based on the extension.
*
* @param file - The file.
* @returns - The mime type.
*/
export function getFileMimeType(file: File): string {
if (file.type) return file.type;
let ext = path.extname(file.name);
ext = trimPrefix(ext, ".");
if (ext !== "") {
const mimeType = mime.getType(ext);
if (mimeType) {
return mimeType;
}
}
return "";
}
/**
* Sometimes file object might have had the type property defined manually with
* Object.defineProperty and some browsers (namely firefox) can have problems
* reading it after the file has been appended to form data. To overcome this,
* we recreate the file object using native File constructor with a type defined
* as a constructor argument.
*
* @param file - The input file.
* @returns - The processed file.
*/
export function ensureFileObjectConsistency(file: File): File {
return new File([file], file.name, { type: getFileMimeType(file) });
}

87
src/utils/options.ts Normal file
View File

@ -0,0 +1,87 @@
import { AxiosHeaders, AxiosProgressEvent, AxiosRequestConfig } from "axios";
import { S5Client } from "../client.js";
import { BaseCustomOptions, CustomRegistryOptions } from "#options/registry.js";
/**
* Custom client options.
*
* @property [apiKey] - Authentication password to use for a single S5 node/portal.
* @property [customUserAgent] - Custom user agent header to set.
* @property [customCookie] - Custom cookie header to set. WARNING: the Cookie header cannot be set in browsers. This is meant for usage in server contexts.
* @property [onDownloadProgress] - Optional callback to track download progress.
* @property [onUploadProgress] - Optional callback to track upload progress.
*/
export type CustomClientOptions = {
apiKey?: string;
customUserAgent?: string;
customCookie?: string;
onDownloadProgress?: (progressEvent: AxiosProgressEvent) => void;
onUploadProgress?: (progressEvent: AxiosProgressEvent) => void;
httpConfig?: AxiosRequestConfig;
};
export function optionsToConfig(
client: S5Client,
def: CustomClientOptions | BaseCustomOptions | CustomRegistryOptions,
...options: (
| CustomClientOptions
| BaseCustomOptions
| CustomRegistryOptions
)[]
): AxiosRequestConfig {
let config: AxiosRequestConfig = {};
config.baseURL = client.portalUrl;
const extraOptions= Object.values(options.reduce((acc, val) => {
return {
...acc,
...val,
};
}, options)).reverse().pop();
const finalOptions = {
...def,
...client.clientOptions,
...extraOptions,
} as CustomClientOptions;
if (finalOptions?.onDownloadProgress) {
config.onDownloadProgress = finalOptions?.onDownloadProgress;
}
if (finalOptions?.onUploadProgress) {
config.onUploadProgress = finalOptions?.onUploadProgress;
}
const headers = new AxiosHeaders(config.headers as AxiosHeaders);
if (finalOptions?.customCookie) {
headers.set("Cookie", finalOptions.customCookie);
}
if (finalOptions?.customUserAgent) {
headers.set("User-Agent", finalOptions.customUserAgent);
}
if (finalOptions?.apiKey) {
headers.set("Authorization", `Bearer ${finalOptions.apiKey}`);
config.withCredentials = true;
config.params = {
...config.params,
auth_token: finalOptions?.apiKey,
};
}
config.headers = headers;
if (finalOptions?.httpConfig) {
config = {
...config,
...finalOptions.httpConfig,
};
}
return config;
}

63
src/utils/string.ts Normal file
View File

@ -0,0 +1,63 @@
/**
* Prepends the prefix to the given string only if the string does not already start with the prefix.
*
* @param str - The string.
* @param prefix - The prefix.
* @returns - The prefixed string.
*/
export function ensurePrefix(str: string, prefix: string): string {
if (!str.startsWith(prefix)) {
str = `${prefix}${str}`;
}
return str;
}
/**
* Removes a prefix from the beginning of the string.
*
* @param str - The string to process.
* @param prefix - The prefix to remove.
* @param [limit] - Maximum amount of times to trim. No limit by default.
* @returns - The processed string.
*/
export function trimPrefix(
str: string,
prefix: string,
limit?: number,
): string {
while (str.startsWith(prefix)) {
if (limit !== undefined && limit <= 0) {
break;
}
str = str.slice(prefix.length);
if (limit) {
limit -= 1;
}
}
return str;
}
/**
* Removes a suffix from the end of the string.
*
* @param str - The string to process.
* @param suffix - The suffix to remove.
* @param [limit] - Maximum amount of times to trim. No limit by default.
* @returns - The processed string.
*/
export function trimSuffix(
str: string,
suffix: string,
limit?: number,
): string {
while (str.endsWith(suffix)) {
if (limit !== undefined && limit <= 0) {
break;
}
str = str.substring(0, str.length - suffix.length);
if (limit) {
limit -= 1;
}
}
return str;
}

97
src/utils/url.ts Normal file
View File

@ -0,0 +1,97 @@
import urljoin from "url-join";
import parse from "url-parse";
import { trimSuffix } from "./string.js";
import { throwValidationError } from "./validation.js";
export const URI_S5_PREFIX = "s5://";
/**
* Adds a subdomain to the given URL.
*
* @param url - The URL.
* @param subdomain - The subdomain to add.
* @returns - The final URL.
*/
export function addUrlSubdomain(url: string, subdomain: string): string {
const urlObj = new URL(url);
urlObj.hostname = `${subdomain}.${urlObj.hostname}`;
const str = urlObj.toString();
return trimSuffix(str, "/");
}
/**
* Adds a query to the given URL.
*
* @param url - The URL.
* @param query - The query parameters.
* @returns - The final URL.
*/
export function addUrlQuery(
url: string,
query: { [key: string]: string | undefined },
): string {
const parsed = parse(url, true);
// Combine the desired query params with the already existing ones.
query = { ...parsed.query, ...query };
parsed.set("query", query);
return parsed.toString();
}
/**
* Prepends the prefix to the given string only if the string does not already start with the prefix.
*
* @param str - The string.
* @param prefix - The prefix.
* @returns - The prefixed string.
*/
export function ensurePrefix(str: string, prefix: string): string {
if (!str.startsWith(prefix)) {
str = `${prefix}${str}`;
}
return str;
}
/**
* Ensures that the given string is a URL.
*
* @param url - The given string.
* @returns - The URL.
*/
export function ensureUrl(url: string): string {
if (url.startsWith("http://")) {
return url;
}
return ensurePrefix(url, "https://");
}
/**
* Ensures that the given string is a URL with a protocol prefix.
*
* @param url - The given string.
* @returns - The URL.
*/
export function ensureUrlPrefix(url: string): string {
if (url === "localhost") {
return "http://localhost/";
}
if (!/^https?:(\/\/)?/i.test(url)) {
return `https://${url}`;
}
return url;
}
/**
* Properly joins paths together to create a URL. Takes a variable number of
* arguments.
*
* @param args - Array of URL parts to join.
* @returns - Final URL constructed from the input parts.
*/
export function makeUrl(...args: string[]): string {
if (args.length === 0) {
throwValidationError("args", args, "parameter", "non-empty");
}
return ensureUrl(args.reduce((acc, cur) => urljoin(acc, cur)));
}

45
src/utils/validation.ts Normal file
View File

@ -0,0 +1,45 @@
/**
* Throws an error for the given value
*
* @param name - The name of the value.
* @param value - The actual value.
* @param valueKind - The kind of value that is being checked (e.g. "parameter", "response field", etc.)
* @param expected - The expected aspect of the value that could not be validated (e.g. "type 'string'" or "non-null").
* @throws - Will always throw.
*/
export function throwValidationError(
name: string,
value: unknown,
valueKind: string,
expected: string,
): void {
throw validationError(name, value, valueKind, expected);
}
/**
* Returns an error for the given value
*
* @param name - The name of the value.
* @param value - The actual value.
* @param valueKind - The kind of value that is being checked (e.g. "parameter", "response field", etc.)
* @param expected - The expected aspect of the value that could not be validated (e.g. "type 'string'" or "non-null").
* @returns - The validation error.
*/
export function validationError(
name: string,
value: unknown,
valueKind: string,
expected: string,
): Error {
let actualValue: string;
if (value === undefined) {
actualValue = "type 'undefined'";
} else if (value === null) {
actualValue = "type 'null'";
} else {
actualValue = `type '${typeof value}', value '${value}'`;
}
return new Error(
`Expected ${valueKind} '${name}' to be ${expected}, was ${actualValue}`,
);
}

1074
swagger.yaml Normal file

File diff suppressed because it is too large Load Diff