Compare commits
1 Commits
Author | SHA1 | Date |
---|---|---|
semantic-release-bot | 50ba311259 |
|
@ -0,0 +1,45 @@
|
||||||
|
version: 2.1
|
||||||
|
|
||||||
|
orbs:
|
||||||
|
node: circleci/node@5.1.0
|
||||||
|
ssh: credijusto/ssh@0.5.2
|
||||||
|
workflows:
|
||||||
|
release:
|
||||||
|
jobs:
|
||||||
|
- node/run:
|
||||||
|
name: build
|
||||||
|
npm-run: build
|
||||||
|
post-steps:
|
||||||
|
- persist_to_workspace:
|
||||||
|
root: .
|
||||||
|
paths:
|
||||||
|
- lib/
|
||||||
|
|
||||||
|
filters:
|
||||||
|
branches:
|
||||||
|
only:
|
||||||
|
- master
|
||||||
|
- develop
|
||||||
|
- /^develop-.*$/
|
||||||
|
- node/run:
|
||||||
|
name: release
|
||||||
|
npm-run: semantic-release
|
||||||
|
requires:
|
||||||
|
- build
|
||||||
|
filters:
|
||||||
|
branches:
|
||||||
|
only:
|
||||||
|
- master
|
||||||
|
- develop
|
||||||
|
- /^develop-.*$/
|
||||||
|
|
||||||
|
context:
|
||||||
|
- publish
|
||||||
|
setup:
|
||||||
|
- attach_workspace:
|
||||||
|
at: ./
|
||||||
|
- add_ssh_keys:
|
||||||
|
fingerprints:
|
||||||
|
- "47:cf:a1:17:d9:81:8e:c5:51:e5:53:c8:33:e4:33:b9"
|
||||||
|
- ssh/ssh-add-host:
|
||||||
|
host_url: GITEA_HOST
|
|
@ -1,13 +0,0 @@
|
||||||
name: Build/Publish
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
- develop
|
|
||||||
- develop-*
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
main:
|
|
||||||
uses: lumeweb/github-node-deploy-workflow/.github/workflows/main.yml@master
|
|
||||||
secrets: inherit
|
|
|
@ -1,3 +1,18 @@
|
||||||
{
|
{
|
||||||
"preset": "@lumeweb/node-library-preset"
|
"preset": "presetter-preset-strict",
|
||||||
|
"config": {
|
||||||
|
"tsconfig": {
|
||||||
|
"compilerOptions": {
|
||||||
|
"lib": [
|
||||||
|
"ES2021"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"prettier": {
|
||||||
|
"singleQuote": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"variable": {
|
||||||
|
"source": "src"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"plugins": [
|
||||||
|
"@semantic-release/commit-analyzer",
|
||||||
|
"@semantic-release/release-notes-generator",
|
||||||
|
[
|
||||||
|
"@semantic-release/changelog"
|
||||||
|
],
|
||||||
|
"@semantic-release/npm",
|
||||||
|
"@semantic-release/git",
|
||||||
|
],
|
||||||
|
"branches": [
|
||||||
|
"master",
|
||||||
|
{
|
||||||
|
name: "develop",
|
||||||
|
prerelease: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "develop-*",
|
||||||
|
prerelease: true
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
316
CHANGELOG.md
316
CHANGELOG.md
|
@ -1,323 +1,9 @@
|
||||||
# [0.2.0-develop.61](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.60...v0.2.0-develop.61) (2023-11-17)
|
# [0.2.0](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.1.3...v0.2.0) (2023-06-21)
|
||||||
|
|
||||||
# [0.2.0-develop.60](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.59...v0.2.0-develop.60) (2023-10-19)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* add savePortalSessions function ([2bc233c](https://git.lumeweb.com/LumeWeb/libweb/commit/2bc233c6b4a8abf5895014a4b8bc819113d763b5))
|
|
||||||
|
|
||||||
# [0.2.0-develop.59](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.58...v0.2.0-develop.59) (2023-09-20)
|
|
||||||
|
|
||||||
# [0.2.0-develop.58](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.57...v0.2.0-develop.58) (2023-09-11)
|
|
||||||
|
|
||||||
# [0.2.0-develop.57](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.56...v0.2.0-develop.57) (2023-09-09)
|
|
||||||
|
|
||||||
# [0.2.0-develop.56](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.55...v0.2.0-develop.56) (2023-09-09)
|
|
||||||
|
|
||||||
# [0.2.0-develop.55](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.54...v0.2.0-develop.55) (2023-09-08)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* need to use CID.decode ([ab1ad68](https://git.lumeweb.com/LumeWeb/libweb/commit/ab1ad68fcb7b2d08842a04ed4c411970f93cfbb4))
|
|
||||||
|
|
||||||
# [0.2.0-develop.54](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.53...v0.2.0-develop.54) (2023-09-08)
|
|
||||||
|
|
||||||
# [0.2.0-develop.53](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.52...v0.2.0-develop.53) (2023-09-08)
|
|
||||||
|
|
||||||
# [0.2.0-develop.52](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.51...v0.2.0-develop.52) (2023-09-08)
|
|
||||||
|
|
||||||
# [0.2.0-develop.51](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.50...v0.2.0-develop.51) (2023-09-08)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* move NO_PORTALS_ERROR to types and re-import ([2c6b843](https://git.lumeweb.com/LumeWeb/libweb/commit/2c6b8438e1cf7b72296af90ddb1bdd767bfda231))
|
|
||||||
* remove export ([9bc7fda](https://git.lumeweb.com/LumeWeb/libweb/commit/9bc7fda44049e129ade3c28fe92d61a290e2d630))
|
|
||||||
* use toRegistryEntry of CID ([841e453](https://git.lumeweb.com/LumeWeb/libweb/commit/841e453a8cbc1ef809e0a2d9eccc64c3ee9aaa4e))
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* add deriveBlakeChildKeyInt ([282c431](https://git.lumeweb.com/LumeWeb/libweb/commit/282c43102196426834f81253511ae7083756380f))
|
|
||||||
* implement appdb, known as hidden db in s5 ([2894e63](https://git.lumeweb.com/LumeWeb/libweb/commit/2894e63aa46bbc66d1b8cd3c59d85f083fc713f5))
|
|
||||||
|
|
||||||
# [0.2.0-develop.50](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.49...v0.2.0-develop.50) (2023-09-04)
|
|
||||||
|
|
||||||
# [0.2.0-develop.49](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.48...v0.2.0-develop.49) (2023-09-04)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* use hashType not type ([108ab86](https://git.lumeweb.com/LumeWeb/libweb/commit/108ab86fcc82cd22d394939e00ec8047eaaab901))
|
|
||||||
|
|
||||||
# [0.2.0-develop.48](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.47...v0.2.0-develop.48) (2023-09-04)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* pass along cid.type, dont force CID_TYPES.RESOLVER ([705590c](https://git.lumeweb.com/LumeWeb/libweb/commit/705590c7c57b18155752acac4bd8cba79bada742))
|
|
||||||
|
|
||||||
# [0.2.0-develop.47](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.46...v0.2.0-develop.47) (2023-09-04)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* encodeRegistryValue needs to use type and hashType ([880a70f](https://git.lumeweb.com/LumeWeb/libweb/commit/880a70f153f5d989cae0385c1111aff37feaf0c5))
|
|
||||||
|
|
||||||
# [0.2.0-develop.46](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.45...v0.2.0-develop.46) (2023-09-04)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* if has is a CID, override arguments with the CID properties unless we have an argument set already ([2c56658](https://git.lumeweb.com/LumeWeb/libweb/commit/2c56658a6c06c51e6f079232c35cce05a6ca72c3))
|
|
||||||
|
|
||||||
# [0.2.0-develop.45](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.44...v0.2.0-develop.45) (2023-09-04)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* return ret not cid ([34e418b](https://git.lumeweb.com/LumeWeb/libweb/commit/34e418b47e4e856bac34f6e5e63ae7f31a733aa6))
|
|
||||||
|
|
||||||
# [0.2.0-develop.44](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.43...v0.2.0-develop.44) (2023-09-04)
|
|
||||||
|
|
||||||
# [0.2.0-develop.43](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.42...v0.2.0-develop.43) (2023-09-04)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* if we have a Uint8Array but invalid CID_HASH_TYPES, return error ([07a451c](https://git.lumeweb.com/LumeWeb/libweb/commit/07a451ce5ecff302bef201b5f40e916e4420c278))
|
|
||||||
|
|
||||||
# [0.2.0-develop.42](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.41...v0.2.0-develop.42) (2023-09-04)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* update encodeCid overloads and return types ([7dc8067](https://git.lumeweb.com/LumeWeb/libweb/commit/7dc8067e958bb62efe856cdb234ac0010b6d2896))
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* add encodeRegistryCid, encodeRegistryValue, decodeRegistryValue, decodeRegistryCid ([7ce52cb](https://git.lumeweb.com/LumeWeb/libweb/commit/7ce52cbff7e622a9046e6a36d4e664d211043e1e))
|
|
||||||
|
|
||||||
# [0.2.0-develop.41](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.40...v0.2.0-develop.41) (2023-09-03)
|
|
||||||
|
|
||||||
# [0.2.0-develop.40](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.39...v0.2.0-develop.40) (2023-09-03)
|
|
||||||
|
|
||||||
# [0.2.0-develop.39](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.38...v0.2.0-develop.39) (2023-09-03)
|
|
||||||
|
|
||||||
# [0.2.0-develop.38](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.37...v0.2.0-develop.38) (2023-09-02)
|
|
||||||
|
|
||||||
# [0.2.0-develop.37](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.36...v0.2.0-develop.37) (2023-09-02)
|
|
||||||
|
|
||||||
# [0.2.0-develop.36](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.35...v0.2.0-develop.36) (2023-09-02)
|
|
||||||
|
|
||||||
# [0.2.0-develop.35](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.34...v0.2.0-develop.35) (2023-09-02)
|
|
||||||
|
|
||||||
# [0.2.0-develop.34](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.33...v0.2.0-develop.34) (2023-09-02)
|
|
||||||
|
|
||||||
# [0.2.0-develop.33](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.32...v0.2.0-develop.33) (2023-09-02)
|
|
||||||
|
|
||||||
# [0.2.0-develop.32](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.31...v0.2.0-develop.32) (2023-08-20)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* change deriveChildKey to hkdf sha256 and create deriveBlakeChildKey that hashes based on the initial blake3 route used by s5 ([7fefaf0](https://git.lumeweb.com/LumeWeb/libweb/commit/7fefaf0818417f1cc4ae0c9f360e5b08734d68f3))
|
|
||||||
|
|
||||||
# [0.2.0-develop.31](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.30...v0.2.0-develop.31) (2023-08-15)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* add loginActivePortals function ([a1b67e7](https://git.lumeweb.com/LumeWeb/libweb/commit/a1b67e71cd51003cae5f51ce927f6f9b6fa46ec6))
|
|
||||||
|
|
||||||
# [0.2.0-develop.30](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.29...v0.2.0-develop.30) (2023-08-10)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* add new downloadSmallObject function that only hashes the data and compares it and does not verify it in real time ([747aeb7](https://git.lumeweb.com/LumeWeb/libweb/commit/747aeb7d2e34b702b764eae367f90b062aabc1f3))
|
|
||||||
|
|
||||||
# [0.2.0-develop.29](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.28...v0.2.0-develop.29) (2023-08-10)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* handle localStorage being undefined ([0ab6cf0](https://git.lumeweb.com/LumeWeb/libweb/commit/0ab6cf0ff095209f0a4d83c4295ecd5558de8a89))
|
|
||||||
|
|
||||||
# [0.2.0-develop.28](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.27...v0.2.0-develop.28) (2023-08-10)
|
|
||||||
|
|
||||||
# [0.2.0-develop.27](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.26...v0.2.0-develop.27) (2023-07-21)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* change to export type ([7f9990c](https://git.lumeweb.com/LumeWeb/libweb/commit/7f9990c2ea4157f83eb87c7c63555bc81db4732a))
|
|
||||||
|
|
||||||
# [0.2.0-develop.26](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.25...v0.2.0-develop.26) (2023-07-18)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* add support for loading and saving portal lists ([4fe84e8](https://git.lumeweb.com/LumeWeb/libweb/commit/4fe84e8ab47d7fadeca685e51182e67f91d4c10f))
|
|
||||||
|
|
||||||
# [0.2.0-develop.25](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.24...v0.2.0-develop.25) (2023-07-18)
|
|
||||||
|
|
||||||
# [0.2.0-develop.24](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.23...v0.2.0-develop.24) (2023-07-18)
|
|
||||||
|
|
||||||
# [0.2.0-develop.23](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.22...v0.2.0-develop.23) (2023-07-18)
|
|
||||||
|
|
||||||
# [0.2.0-develop.22](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.21...v0.2.0-develop.22) (2023-07-08)
|
|
||||||
|
|
||||||
# [0.2.0-develop.21](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.20...v0.2.0-develop.21) (2023-07-02)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* export bufToHex ([0fead1c](https://git.lumeweb.com/LumeWeb/libweb/commit/0fead1c78628a049ff25b69cd586fb3d03b0e29e))
|
|
||||||
|
|
||||||
# [0.2.0-develop.20](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.19...v0.2.0-develop.20) (2023-06-29)
|
|
||||||
|
|
||||||
# [0.2.0-develop.19](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.18...v0.2.0-develop.19) (2023-06-26)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* switch to using globalThis, and check if we have localStorage (if not, might be node) ([79273d2](https://git.lumeweb.com/LumeWeb/libweb/commit/79273d263b9f65dbe4faf369fa936e8d746d81d2))
|
|
||||||
|
|
||||||
# [0.2.0-develop.18](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.17...v0.2.0-develop.18) (2023-06-26)
|
|
||||||
|
|
||||||
# [0.2.0-develop.17](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.16...v0.2.0-develop.17) (2023-06-26)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* re-export most of noble utility functions ([a813ee3](https://git.lumeweb.com/LumeWeb/libweb/commit/a813ee35f359a6f019d53b3bad0d9585dcc0d23e))
|
|
||||||
|
|
||||||
# [0.2.0-develop.16](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.15...v0.2.0-develop.16) (2023-06-26)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* add initial upload method ([7126203](https://git.lumeweb.com/LumeWeb/libweb/commit/7126203cd3c97485de422e8759442a04074a8f64))
|
|
||||||
|
|
||||||
# [0.2.0-develop.15](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.14...v0.2.0-develop.15) (2023-06-25)
|
|
||||||
|
|
||||||
# [0.2.0-develop.14](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.13...v0.2.0-develop.14) (2023-06-24)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* switch to using @lumeweb/community-portals ([ac822fb](https://git.lumeweb.com/LumeWeb/libweb/commit/ac822fb9390cfc863a959dc09269479d73a71b80))
|
|
||||||
|
|
||||||
# [0.2.0-develop.13](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.12...v0.2.0-develop.13) (2023-06-24)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* update libportal ([6d2311b](https://git.lumeweb.com/LumeWeb/libweb/commit/6d2311b9dab8b04df3e468b5b9f3932b9b18d0c7))
|
|
||||||
* update npm-npm-shrinkwrap.json ([0d042f7](https://git.lumeweb.com/LumeWeb/libweb/commit/0d042f78673a61585683cdfc8518daa474da158e))
|
|
||||||
|
|
||||||
# [0.2.0-develop.12](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.11...v0.2.0-develop.12) (2023-06-24)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* don't skip portal if we fail to register ([530b159](https://git.lumeweb.com/LumeWeb/libweb/commit/530b159c96b5426c7c5c891c8773f6e9afb03685))
|
|
||||||
|
|
||||||
# [0.2.0-develop.11](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.10...v0.2.0-develop.11) (2023-06-24)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* update libportal ([b6e30e1](https://git.lumeweb.com/LumeWeb/libweb/commit/b6e30e164584dbd4fadd48a1362d3cbbeb04ece7))
|
|
||||||
|
|
||||||
# [0.2.0-develop.10](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.9...v0.2.0-develop.10) (2023-06-24)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* update libportal ([80be9e6](https://git.lumeweb.com/LumeWeb/libweb/commit/80be9e64316782d6aeb4f9c3245352ca7b65907d))
|
|
||||||
|
|
||||||
# [0.2.0-develop.9](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.8...v0.2.0-develop.9) (2023-06-23)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* refactor initPortal to return the instance ([89d2439](https://git.lumeweb.com/LumeWeb/libweb/commit/89d24393e5ec8c3d0846b00bec2912dd177e34a8))
|
|
||||||
|
|
||||||
# [0.2.0-develop.8](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.7...v0.2.0-develop.8) (2023-06-23)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* make getActivePortals return an array, not a set ([d8e2046](https://git.lumeweb.com/LumeWeb/libweb/commit/d8e2046ebda8b428cfcd5470d63a9c09a6047819))
|
|
||||||
* need to use length, not size ([5db1217](https://git.lumeweb.com/LumeWeb/libweb/commit/5db121774ebf7c4eec4360a299cfb54991091747))
|
|
||||||
|
|
||||||
# [0.2.0-develop.7](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.6...v0.2.0-develop.7) (2023-06-23)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* add portal to exports ([8b7f708](https://git.lumeweb.com/LumeWeb/libweb/commit/8b7f7082e3af84d8963ec535804b8816a1a396dc))
|
|
||||||
* improve portal api ([ca43e88](https://git.lumeweb.com/LumeWeb/libweb/commit/ca43e883006a58d92eec519925a007c0c82c55c6))
|
|
||||||
|
|
||||||
# [0.2.0-develop.6](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.5...v0.2.0-develop.6) (2023-06-23)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* update libportal ([bd4f10a](https://git.lumeweb.com/LumeWeb/libweb/commit/bd4f10ad619b218f801fc5ce31be28adbf97e584))
|
|
||||||
|
|
||||||
# [0.2.0-develop.5](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.4...v0.2.0-develop.5) (2023-06-23)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* ensure we are using the export for @noble/hashes utils ([7dd6f9f](https://git.lumeweb.com/LumeWeb/libweb/commit/7dd6f9f0b0524b86e851add7e825b9dd0ac3a7c8))
|
|
||||||
* update libportal ([100a9f2](https://git.lumeweb.com/LumeWeb/libweb/commit/100a9f2d6c75370ee909dd4fe6e574b19166d1f2))
|
|
||||||
|
|
||||||
# [0.2.0-develop.4](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.3...v0.2.0-develop.4) (2023-06-23)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* update libportal ([5bb6c99](https://git.lumeweb.com/LumeWeb/libweb/commit/5bb6c99f4c16c13872886b259c27484e7d2c7fca))
|
|
||||||
|
|
||||||
# [0.2.0-develop.3](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.2...v0.2.0-develop.3) (2023-06-23)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* add download to exports ([a27bb31](https://git.lumeweb.com/LumeWeb/libweb/commit/a27bb31336c41002780975edc0168ad70bc0a32a))
|
|
||||||
|
|
||||||
|
|
||||||
### Reverts
|
|
||||||
|
|
||||||
* Revert "fix: add download to exports" ([e2bcb8c](https://git.lumeweb.com/LumeWeb/libweb/commit/e2bcb8cf3d366f5f8dd27b3dfff7fb91de59e822))
|
|
||||||
|
|
||||||
# [0.2.0-develop.2](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.2.0-develop.1...v0.2.0-develop.2) (2023-06-23)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* add download to exports ([4f2ec80](https://git.lumeweb.com/LumeWeb/libweb/commit/4f2ec806e1c46b23aaf3269f75a9fe2958e233fc))
|
|
||||||
|
|
||||||
# [0.2.0-develop.1](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.1.3...v0.2.0-develop.1) (2023-06-23)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* need to add dom support to TS ([738c3f1](https://git.lumeweb.com/LumeWeb/libweb/commit/738c3f12cfb49456fbcdd433b3f4bd30daa031b7))
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
* add deriveChildKey function ([d7cdaaf](https://git.lumeweb.com/LumeWeb/libweb/commit/d7cdaaf316d4d26ed44860701376d18030030708))
|
* add deriveChildKey function ([d7cdaaf](https://git.lumeweb.com/LumeWeb/libweb/commit/d7cdaaf316d4d26ed44860701376d18030030708))
|
||||||
* add portal management apis ([d340447](https://git.lumeweb.com/LumeWeb/libweb/commit/d340447aba098dbac6163bfbacca0429323e6e45))
|
|
||||||
* implement initial download method ([7c07211](https://git.lumeweb.com/LumeWeb/libweb/commit/7c07211356497ed36119d32943c46cb2e268b30f))
|
|
||||||
|
|
||||||
## [0.1.3](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.1.2...v0.1.3) (2023-06-21)
|
## [0.1.3](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.1.2...v0.1.3) (2023-06-21)
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@lumeweb/libweb",
|
"name": "@lumeweb/libweb",
|
||||||
"version": "0.2.0-develop.61",
|
"version": "0.2.0",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -21,13 +21,9 @@
|
||||||
"semantic-release": "semantic-release"
|
"semantic-release": "semantic-release"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@lumeweb/community-portals": "^0.1.0-develop.6",
|
"@lumeweb/libportal": "^0.1.0",
|
||||||
"@lumeweb/libportal": "0.2.0-develop.41",
|
|
||||||
"@lumeweb/node-library-preset": "0.2.7",
|
|
||||||
"@noble/ciphers": "^0.3.0",
|
|
||||||
"@noble/curves": "^1.1.0",
|
"@noble/curves": "^1.1.0",
|
||||||
"@noble/hashes": "^1.3.1",
|
"@noble/hashes": "^1.3.1"
|
||||||
"binconv": "^0.2.0"
|
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
|
|
176
src/appDb.ts
176
src/appDb.ts
|
@ -1,176 +0,0 @@
|
||||||
import {
|
|
||||||
CID,
|
|
||||||
createKeyPair,
|
|
||||||
S5Node,
|
|
||||||
encryptionKeyDerivationTweak,
|
|
||||||
pathKeyDerivationTweak,
|
|
||||||
writeKeyDerivationTweak,
|
|
||||||
} from "@lumeweb/libs5";
|
|
||||||
import { deriveBlakeChildKey, deriveBlakeChildKeyInt } from "#keys.js";
|
|
||||||
import { uploadObject } from "#upload.js";
|
|
||||||
import { signRegistryEntry } from "@lumeweb/libs5/lib/service/registry.js";
|
|
||||||
import { downloadSmallObject } from "#download.js";
|
|
||||||
import { readableStreamToUint8Array } from "binconv";
|
|
||||||
import { utf8ToBytes } from "@noble/hashes/utils";
|
|
||||||
import { decryptMutableBytes, encryptMutableBytes } from "#encryption.js";
|
|
||||||
|
|
||||||
export default class AppDb {
|
|
||||||
private readonly _rootKey: Uint8Array;
|
|
||||||
private readonly _node: S5Node;
|
|
||||||
private readonly _cidMap: Map<string, CID> = new Map<string, CID>();
|
|
||||||
|
|
||||||
constructor(rootKey: Uint8Array, api: S5Node) {
|
|
||||||
this._rootKey = rootKey;
|
|
||||||
this._node = api;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getRawData(path: string): Promise<AppDbRawDataResponse> {
|
|
||||||
const pathKey = this._derivePathKeyForPath(path);
|
|
||||||
const encryptionKey = deriveBlakeChildKeyInt(
|
|
||||||
pathKey,
|
|
||||||
encryptionKeyDerivationTweak,
|
|
||||||
);
|
|
||||||
|
|
||||||
const writeKey = deriveBlakeChildKeyInt(pathKey, writeKeyDerivationTweak);
|
|
||||||
const keyPair = await createKeyPair(writeKey);
|
|
||||||
|
|
||||||
const sre = await this._node.services.registry.get(keyPair.publicKey);
|
|
||||||
if (!sre) {
|
|
||||||
return new AppDbRawDataResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
const cid = CID.fromBytes(sre.data.slice(1));
|
|
||||||
|
|
||||||
const bytes = await downloadSmallObject(cid.toString());
|
|
||||||
|
|
||||||
const plaintext = await decryptMutableBytes(
|
|
||||||
await readableStreamToUint8Array(bytes),
|
|
||||||
encryptionKey,
|
|
||||||
);
|
|
||||||
|
|
||||||
this._cidMap.set(path, cid);
|
|
||||||
|
|
||||||
return new AppDbRawDataResponse({
|
|
||||||
data: plaintext,
|
|
||||||
cid,
|
|
||||||
revision: sre.revision,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async setRawData(
|
|
||||||
path: string,
|
|
||||||
data: Uint8Array,
|
|
||||||
revision: number,
|
|
||||||
): Promise<void> {
|
|
||||||
const pathKey = this._derivePathKeyForPath(path);
|
|
||||||
const encryptionKey = deriveBlakeChildKeyInt(
|
|
||||||
pathKey,
|
|
||||||
encryptionKeyDerivationTweak,
|
|
||||||
);
|
|
||||||
|
|
||||||
const cipherText = await encryptMutableBytes(data, encryptionKey);
|
|
||||||
|
|
||||||
const cid = await uploadObject(cipherText);
|
|
||||||
const writeKey = deriveBlakeChildKeyInt(pathKey, writeKeyDerivationTweak);
|
|
||||||
const keyPair = createKeyPair(writeKey);
|
|
||||||
|
|
||||||
const sre = await signRegistryEntry({
|
|
||||||
kp: keyPair,
|
|
||||||
data: cid.toRegistryEntry(),
|
|
||||||
revision,
|
|
||||||
});
|
|
||||||
|
|
||||||
await this._node.services.registry.set(sre);
|
|
||||||
|
|
||||||
this._cidMap.set(path, cid);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getJSON(path: string): Promise<AppDbJSONResponse> {
|
|
||||||
const res = await this.getRawData(path);
|
|
||||||
|
|
||||||
if (res.data === null) {
|
|
||||||
return new AppDbJSONResponse({ cid: res.cid });
|
|
||||||
}
|
|
||||||
|
|
||||||
const decodedData = JSON.parse(new TextDecoder().decode(res.data));
|
|
||||||
return new AppDbJSONResponse({
|
|
||||||
data: decodedData,
|
|
||||||
revision: res.revision,
|
|
||||||
cid: res.cid,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async setJSON(
|
|
||||||
path: string,
|
|
||||||
data: any,
|
|
||||||
revision: number,
|
|
||||||
): Promise<void> {
|
|
||||||
const encodedData = utf8ToBytes(JSON.stringify(data));
|
|
||||||
await this.setRawData(path, new Uint8Array(encodedData), revision);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _derivePathKeyForPath(path: string): Uint8Array {
|
|
||||||
const pathSegments = path
|
|
||||||
.split("/")
|
|
||||||
.map((e) => e.trim())
|
|
||||||
.filter((element) => element.length > 0);
|
|
||||||
|
|
||||||
const key = this._deriveKeyForPathSegments(pathSegments);
|
|
||||||
return deriveBlakeChildKeyInt(key, pathKeyDerivationTweak);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _deriveKeyForPathSegments(pathSegments: string[]): Uint8Array {
|
|
||||||
if (pathSegments.length === 0) {
|
|
||||||
return this._rootKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
return deriveBlakeChildKey(
|
|
||||||
this._deriveKeyForPathSegments(
|
|
||||||
pathSegments.slice(0, pathSegments.length - 1),
|
|
||||||
),
|
|
||||||
pathSegments[pathSegments.length - 1],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class AppDbRawDataResponse {
|
|
||||||
data: Uint8Array | undefined;
|
|
||||||
revision: number;
|
|
||||||
cid: CID | undefined;
|
|
||||||
|
|
||||||
constructor({
|
|
||||||
data,
|
|
||||||
revision = -1,
|
|
||||||
cid,
|
|
||||||
}: {
|
|
||||||
data?: Uint8Array;
|
|
||||||
revision?: number;
|
|
||||||
cid?: CID;
|
|
||||||
} = {}) {
|
|
||||||
this.data = data;
|
|
||||||
this.revision = revision || -1;
|
|
||||||
this.cid = cid;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class AppDbJSONResponse {
|
|
||||||
data: any;
|
|
||||||
revision: number;
|
|
||||||
cid: CID | undefined;
|
|
||||||
|
|
||||||
constructor({
|
|
||||||
data,
|
|
||||||
revision = -1,
|
|
||||||
cid,
|
|
||||||
}: {
|
|
||||||
data?: any;
|
|
||||||
revision?: number;
|
|
||||||
cid?: CID;
|
|
||||||
} = {}) {
|
|
||||||
this.data = data;
|
|
||||||
this.revision = revision || -1;
|
|
||||||
this.cid = cid;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export { AppDbRawDataResponse, AppDbJSONResponse };
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
import { ErrTuple } from "#types.js";
|
||||||
|
import {
|
||||||
|
decodeCid as decodeCidPortal,
|
||||||
|
encodeCid as encodeCidPortal,
|
||||||
|
} from "@lumeweb/libportal";
|
||||||
|
import { addContextToErr } from "#err.js";
|
||||||
|
|
||||||
|
export function encodeCid(hash: Uint8Array, size: bigint): any;
|
||||||
|
export function encodeCid(hash: string, size: bigint): any;
|
||||||
|
export function encodeCid(hash: any, size: bigint): ErrTuple {
|
||||||
|
try {
|
||||||
|
return [encodeCidPortal(hash, size), null];
|
||||||
|
} catch (e) {
|
||||||
|
return [null, addContextToErr(e as Error, "failed to encode cid")];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function decodeCid(cid: string): ErrTuple {
|
||||||
|
try {
|
||||||
|
return [decodeCidPortal(cid), null];
|
||||||
|
} catch (e) {
|
||||||
|
return [null, addContextToErr(e as Error, "failed to decode cid")];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function verifyCid(cid: string): boolean {
|
||||||
|
try {
|
||||||
|
decodeCidPortal(cid);
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { CID } from "@lumeweb/libportal";
|
|
@ -1,83 +1 @@
|
||||||
import { getActivePortals } from "#portal.js";
|
export function downloadObject(cid: string) {}
|
||||||
import { getVerifiableStream } from "@lumeweb/libportal";
|
|
||||||
import {
|
|
||||||
readableStreamToUint8Array,
|
|
||||||
uint8ArrayToReadableStream,
|
|
||||||
} from "binconv";
|
|
||||||
import { equalBytes } from "@noble/curves/abstract/utils";
|
|
||||||
import { blake3 } from "@noble/hashes/blake3";
|
|
||||||
import { NO_PORTALS_ERROR } from "#types.js";
|
|
||||||
import { CID } from "@lumeweb/libs5";
|
|
||||||
|
|
||||||
export async function downloadObject(cid: string): Promise<ReadableStream> {
|
|
||||||
const activePortals = getActivePortals();
|
|
||||||
|
|
||||||
if (!activePortals.length) {
|
|
||||||
throw NO_PORTALS_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const portal of activePortals) {
|
|
||||||
if (!(await portal.isLoggedIn())) {
|
|
||||||
try {
|
|
||||||
await portal.register();
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
await portal.login();
|
|
||||||
}
|
|
||||||
|
|
||||||
let stream, proof;
|
|
||||||
|
|
||||||
try {
|
|
||||||
stream = await portal.downloadFile(cid);
|
|
||||||
proof = await portal.downloadProof(cid);
|
|
||||||
} catch {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
return await getVerifiableStream(
|
|
||||||
CID.decode(cid).hash.hashBytes,
|
|
||||||
proof,
|
|
||||||
stream,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw NO_PORTALS_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function downloadSmallObject(
|
|
||||||
cid: string,
|
|
||||||
): Promise<ReadableStream<Uint8Array>> {
|
|
||||||
const activePortals = getActivePortals();
|
|
||||||
|
|
||||||
if (!activePortals.length) {
|
|
||||||
throw NO_PORTALS_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const portal of activePortals) {
|
|
||||||
if (!(await portal.isLoggedIn())) {
|
|
||||||
try {
|
|
||||||
await portal.register();
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
await portal.login();
|
|
||||||
}
|
|
||||||
|
|
||||||
let stream;
|
|
||||||
|
|
||||||
try {
|
|
||||||
stream = await portal.downloadFile(cid);
|
|
||||||
} catch {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await readableStreamToUint8Array(stream);
|
|
||||||
|
|
||||||
if (!equalBytes(blake3(data), CID.decode(cid).hash.hashBytes)) {
|
|
||||||
throw new Error("cid verification failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
return uint8ArrayToReadableStream(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw NO_PORTALS_ERROR;
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,130 @@
|
||||||
|
import { addContextToErr } from "./err.js";
|
||||||
|
import { Err } from "./types.js";
|
||||||
|
|
||||||
|
const MAX_UINT_64 = 18446744073709551615n;
|
||||||
|
|
||||||
|
// b64ToBuf will take an untrusted base64 string and convert it into a
|
||||||
|
// Uin8Array, returning an error if the input is not valid base64.
|
||||||
|
const b64regex = /^[0-9a-zA-Z-_/+=]*$/;
|
||||||
|
function b64ToBuf(b64: string): [Uint8Array, Err] {
|
||||||
|
// Check that the final string is valid base64.
|
||||||
|
if (!b64regex.test(b64)) {
|
||||||
|
return [new Uint8Array(0), "provided string is not valid base64"];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap any '-' characters for '+', and swap any '_' characters for '/'
|
||||||
|
// for use in the atob function.
|
||||||
|
b64 = b64.replaceAll("-", "+").replaceAll("_", "/");
|
||||||
|
|
||||||
|
// Perform the conversion.
|
||||||
|
const binStr = atob(b64);
|
||||||
|
const len = binStr.length;
|
||||||
|
const buf = new Uint8Array(len);
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
buf[i] = binStr.charCodeAt(i);
|
||||||
|
}
|
||||||
|
return [buf, null];
|
||||||
|
}
|
||||||
|
|
||||||
|
// bufToHex takes a Uint8Array as input and returns the hex encoding of those
|
||||||
|
// bytes as a string.
|
||||||
|
function bufToHex(buf: Uint8Array): string {
|
||||||
|
return [...buf].map((x) => x.toString(16).padStart(2, "0")).join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
// bufToB64 will convert a Uint8Array to a base64 string with URL encoding and
|
||||||
|
// no padding characters.
|
||||||
|
function bufToB64(buf: Uint8Array): string {
|
||||||
|
const b64Str = btoa(String.fromCharCode(...buf));
|
||||||
|
return b64Str.replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
// bufToStr takes an ArrayBuffer as input and returns a text string. bufToStr
|
||||||
|
// will check for invalid characters.
|
||||||
|
function bufToStr(buf: ArrayBuffer): [string, Err] {
|
||||||
|
try {
|
||||||
|
const text = new TextDecoder("utf-8", { fatal: true }).decode(buf);
|
||||||
|
return [text, null];
|
||||||
|
} catch (err: any) {
|
||||||
|
return [
|
||||||
|
"",
|
||||||
|
addContextToErr(err.toString(), "unable to decode ArrayBuffer to string"),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodeU64 is the opposite of encodeU64, it takes a uint64 encoded as 8 bytes
|
||||||
|
// and decodes them into a BigInt.
|
||||||
|
function decodeU64(u8: Uint8Array): [bigint, Err] {
|
||||||
|
// Check the input.
|
||||||
|
if (u8.length !== 8) {
|
||||||
|
return [0n, "input should be 8 bytes"];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process the input.
|
||||||
|
let num = 0n;
|
||||||
|
for (let i = u8.length - 1; i >= 0; i--) {
|
||||||
|
num *= 256n;
|
||||||
|
num += BigInt(u8[i]);
|
||||||
|
}
|
||||||
|
return [num, null];
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeU64 will encode a bigint in the range of a uint64 to an 8 byte
|
||||||
|
// Uint8Array.
|
||||||
|
function encodeU64(num: bigint): [Uint8Array, Err] {
|
||||||
|
// Check the bounds on the bigint.
|
||||||
|
if (num < 0) {
|
||||||
|
return [new Uint8Array(0), "expected a positive integer"];
|
||||||
|
}
|
||||||
|
if (num > MAX_UINT_64) {
|
||||||
|
return [new Uint8Array(0), "expected a number no larger than a uint64"];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode the bigint into a Uint8Array.
|
||||||
|
const encoded = new Uint8Array(8);
|
||||||
|
for (let i = 0; i < encoded.length; i++) {
|
||||||
|
encoded[i] = Number(num & 0xffn);
|
||||||
|
num = num >> 8n;
|
||||||
|
}
|
||||||
|
return [encoded, null];
|
||||||
|
}
|
||||||
|
|
||||||
|
// hexToBuf takes an untrusted string as input, verifies that the string is
|
||||||
|
// valid hex, and then converts the string to a Uint8Array.
|
||||||
|
const allHex = /^[0-9a-f]+$/i;
|
||||||
|
function hexToBuf(hex: string): [Uint8Array, Err] {
|
||||||
|
// The rest of the code doesn't handle zero length input well, so we handle
|
||||||
|
// that separately. It's not an error, we just return an empty array.
|
||||||
|
if (hex.length === 0) {
|
||||||
|
return [new Uint8Array(0), null];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the length makes sense.
|
||||||
|
if (hex.length % 2 !== 0) {
|
||||||
|
return [new Uint8Array(0), "input has incorrect length"];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that all of the characters are legal.
|
||||||
|
if (!allHex.test(hex)) {
|
||||||
|
return [new Uint8Array(0), "input has invalid character"];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the buffer and fill it.
|
||||||
|
const matches = hex.match(/.{2}/g);
|
||||||
|
if (matches === null) {
|
||||||
|
return [new Uint8Array(0), "input is incomplete"];
|
||||||
|
}
|
||||||
|
const u8 = new Uint8Array(matches.map((byte) => parseInt(byte, 16)));
|
||||||
|
return [u8, null];
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
b64ToBuf,
|
||||||
|
bufToHex,
|
||||||
|
bufToB64,
|
||||||
|
bufToStr,
|
||||||
|
decodeU64,
|
||||||
|
encodeU64,
|
||||||
|
hexToBuf,
|
||||||
|
};
|
|
@ -1,109 +0,0 @@
|
||||||
import {
|
|
||||||
decodeEndian,
|
|
||||||
encodeEndian,
|
|
||||||
encryptionKeyLength,
|
|
||||||
encryptionNonceLength,
|
|
||||||
encryptionOverheadLength,
|
|
||||||
} from "@lumeweb/libs5";
|
|
||||||
|
|
||||||
import { xchacha20poly1305 } from "@noble/ciphers/chacha";
|
|
||||||
import { randomBytes } from "@noble/ciphers/webcrypto/utils";
|
|
||||||
|
|
||||||
function padFileSizeDefault(initialSize: number): number {
|
|
||||||
const kib = 1 << 10;
|
|
||||||
// Only iterate to 53 (the maximum safe power of 2).
|
|
||||||
for (let n = 0; n < 53; n++) {
|
|
||||||
if (initialSize <= (1 << n) * 80 * kib) {
|
|
||||||
const paddingBlock = (1 << n) * 4 * kib;
|
|
||||||
let finalSize = initialSize;
|
|
||||||
if (finalSize % paddingBlock !== 0) {
|
|
||||||
finalSize = initialSize - (initialSize % paddingBlock) + paddingBlock;
|
|
||||||
}
|
|
||||||
return finalSize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Prevent overflow.
|
|
||||||
throw new Error("Could not pad file size, overflow detected.");
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkPaddedBlock(size: number): boolean {
|
|
||||||
const kib = 1024;
|
|
||||||
// Only iterate to 53 (the maximum safe power of 2).
|
|
||||||
for (let n = 0; n < 53; n++) {
|
|
||||||
if (size <= (1 << n) * 80 * kib) {
|
|
||||||
const paddingBlock = (1 << n) * 4 * kib;
|
|
||||||
return size % paddingBlock === 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new Error("Could not check padded file size, overflow detected.");
|
|
||||||
}
|
|
||||||
|
|
||||||
async function encryptMutableBytes(
|
|
||||||
data: Uint8Array,
|
|
||||||
key: Uint8Array,
|
|
||||||
): Promise<Uint8Array> {
|
|
||||||
const lengthInBytes = encodeEndian(data.length, 4);
|
|
||||||
|
|
||||||
const totalOverhead =
|
|
||||||
encryptionOverheadLength + 4 + encryptionNonceLength + 2;
|
|
||||||
|
|
||||||
const finalSize =
|
|
||||||
padFileSizeDefault(data.length + totalOverhead) - totalOverhead;
|
|
||||||
|
|
||||||
data = new Uint8Array([
|
|
||||||
...lengthInBytes,
|
|
||||||
...data,
|
|
||||||
...new Uint8Array(finalSize - data.length),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const nonce = randomBytes(encryptionNonceLength);
|
|
||||||
|
|
||||||
const header = new Uint8Array([0x8d, 0x01, ...nonce]);
|
|
||||||
|
|
||||||
const stream_x = xchacha20poly1305(key, nonce);
|
|
||||||
|
|
||||||
const encryptedBytes = stream_x.encrypt(data);
|
|
||||||
|
|
||||||
return new Uint8Array([...header, ...encryptedBytes]);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function decryptMutableBytes(
|
|
||||||
data: Uint8Array,
|
|
||||||
key: Uint8Array,
|
|
||||||
): Promise<Uint8Array> {
|
|
||||||
if (key.length !== encryptionKeyLength) {
|
|
||||||
throw `wrong encryptionKeyLength (${key.length} != ${encryptionKeyLength})`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!checkPaddedBlock(data.length)) {
|
|
||||||
throw `Expected parameter 'data' to be padded encrypted data, length was '${
|
|
||||||
data.length
|
|
||||||
}', nearest padded block is '${padFileSizeDefault(data.length)}'`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const version = data[1];
|
|
||||||
if (version !== 0x01) {
|
|
||||||
throw "Invalid version";
|
|
||||||
}
|
|
||||||
|
|
||||||
const nonce = data.subarray(2, encryptionNonceLength + 2);
|
|
||||||
|
|
||||||
const stream_x = xchacha20poly1305(key, nonce);
|
|
||||||
|
|
||||||
const decryptedBytes = stream_x.decrypt(
|
|
||||||
data.subarray(encryptionNonceLength + 2),
|
|
||||||
);
|
|
||||||
|
|
||||||
const lengthInBytes = decryptedBytes.subarray(0, 4);
|
|
||||||
|
|
||||||
const length = decodeEndian(lengthInBytes);
|
|
||||||
|
|
||||||
return decryptedBytes.subarray(4, length + 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
|
||||||
padFileSizeDefault,
|
|
||||||
checkPaddedBlock,
|
|
||||||
decryptMutableBytes,
|
|
||||||
encryptMutableBytes,
|
|
||||||
};
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { objAsString } from "./objAsString.js";
|
||||||
|
|
||||||
|
// addContextToErr is a helper function that standardizes the formatting of
|
||||||
|
// adding context to an error.
|
||||||
|
//
|
||||||
|
// NOTE: To protect against accidental situations where an Error type or some
|
||||||
|
// other type is provided instead of a string, we wrap both of the inputs with
|
||||||
|
// objAsString before returning them. This prevents runtime failures.
|
||||||
|
function addContextToErr(err: any, context: string): string {
|
||||||
|
if (err === null || err === undefined) {
|
||||||
|
err = "[no error provided]";
|
||||||
|
}
|
||||||
|
return objAsString(context) + ": " + objAsString(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { addContextToErr };
|
|
@ -0,0 +1,101 @@
|
||||||
|
// errTracker.ts defines an 'ErrTracker' type which keeps track of historical
|
||||||
|
// errors. When the number of errors gets too large, it randomly starts pruning
|
||||||
|
// errors. It always keeps 250 of the most recent errors, and then keeps up to
|
||||||
|
// 500 historic errors, where the first few errors after runtime are always
|
||||||
|
// kept, and the ones in the middle are increasingly likely to be omitted from
|
||||||
|
// the history.
|
||||||
|
|
||||||
|
import { Err } from "./types.js";
|
||||||
|
|
||||||
|
// MAX_ERRORS defines the maximum number of errors that will be held in the
|
||||||
|
// HistoricErr object.
|
||||||
|
const MAX_ERRORS = 1000;
|
||||||
|
|
||||||
|
// HistoricErr is a wrapper that adds a date to the Err type.
|
||||||
|
interface HistoricErr {
|
||||||
|
err: Err;
|
||||||
|
date: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrTracker keeps track of errors that have happened, randomly dropping
|
||||||
|
// errors to prevent the tracker from using too much memory if there happen to
|
||||||
|
// be a large number of errors.
|
||||||
|
interface ErrTracker {
|
||||||
|
recentErrs: HistoricErr[];
|
||||||
|
oldErrs: HistoricErr[];
|
||||||
|
|
||||||
|
addErr: (err: Err) => void;
|
||||||
|
viewErrs: () => HistoricErr[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// newErrTracker returns an ErrTracker object that is ready to have errors
|
||||||
|
// added to it.
|
||||||
|
function newErrTracker(): ErrTracker {
|
||||||
|
const et: ErrTracker = {
|
||||||
|
recentErrs: [],
|
||||||
|
oldErrs: [],
|
||||||
|
|
||||||
|
addErr: function (err: Err): void {
|
||||||
|
addHistoricErr(et, err);
|
||||||
|
},
|
||||||
|
viewErrs: function (): HistoricErr[] {
|
||||||
|
return viewErrs(et);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return et;
|
||||||
|
}
|
||||||
|
|
||||||
|
// addHistoricErr is a function that will add an error to a set of historic
|
||||||
|
// errors. It uses randomness to prune errors once the error object is too
|
||||||
|
// large.
|
||||||
|
function addHistoricErr(et: ErrTracker, err: Err): void {
|
||||||
|
// Add this error to the set of most recent errors.
|
||||||
|
et.recentErrs.push({
|
||||||
|
err,
|
||||||
|
date: new Date(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Determine whether some of the most recent errors need to be moved into
|
||||||
|
// logTermErrs. If the length of the mostRecentErrs is not at least half of
|
||||||
|
// the MAX_ERRORS, we don't need to do anything.
|
||||||
|
if (et.recentErrs.length < MAX_ERRORS / 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate through the recentErrs. For the first half of the recentErrs, we
|
||||||
|
// will use randomness to either toss them or move them to oldErrs. The
|
||||||
|
// second half of the recentErrs will be kept as the new recentErrs array.
|
||||||
|
const newRecentErrs : HistoricErr[] = [];
|
||||||
|
for (let i = 0; i < et.recentErrs.length; i++) {
|
||||||
|
// If we are in the second half of the array, add the element to
|
||||||
|
// newRecentErrs.
|
||||||
|
if (i > et.recentErrs.length / 2) {
|
||||||
|
newRecentErrs.push(et.recentErrs[i]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We are in the first half of the array, use a random number to add the
|
||||||
|
// error oldErrs probabilistically.
|
||||||
|
const rand = Math.random();
|
||||||
|
const target = et.oldErrs.length / (MAX_ERRORS / 2);
|
||||||
|
if (rand > target || et.oldErrs.length < 25) {
|
||||||
|
et.oldErrs.push(et.recentErrs[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
et.recentErrs = newRecentErrs;
|
||||||
|
}
|
||||||
|
|
||||||
|
// viewErrs returns the list of errors that have been retained by the
|
||||||
|
// HistoricErr object.
|
||||||
|
function viewErrs(et: ErrTracker): HistoricErr[] {
|
||||||
|
const finalErrs: HistoricErr[] = [];
|
||||||
|
for (let i = 0; i < et.oldErrs.length; i++) {
|
||||||
|
finalErrs.push(et.oldErrs[i]);
|
||||||
|
}
|
||||||
|
for (let i = 0; i < et.recentErrs.length; i++) {
|
||||||
|
finalErrs.push(et.recentErrs[i]);
|
||||||
|
}
|
||||||
|
return finalErrs;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { ErrTracker, HistoricErr, newErrTracker };
|
38
src/index.ts
38
src/index.ts
|
@ -1,35 +1,13 @@
|
||||||
import { ed25519 } from "@noble/curves/ed25519";
|
import { ed25519 } from "@noble/curves/ed25519";
|
||||||
import { sha512 } from "@noble/hashes/sha512";
|
import { sha512 } from "@noble/hashes/sha512";
|
||||||
|
|
||||||
import AppDb from "#appDb.js";
|
export * from "./err.js";
|
||||||
|
export * from "./errTracker.js";
|
||||||
export {
|
export * from "./objAsString.js";
|
||||||
bytesToHex,
|
export * from "./parse.js";
|
||||||
hexToBytes,
|
export * from "./stringifyJSON.js";
|
||||||
toBytes,
|
|
||||||
concatBytes,
|
|
||||||
randomBytes,
|
|
||||||
} from "@noble/hashes/utils";
|
|
||||||
export {
|
|
||||||
numberToHexUnpadded,
|
|
||||||
hexToNumber,
|
|
||||||
bytesToNumberBE,
|
|
||||||
bytesToNumberLE,
|
|
||||||
numberToBytesBE,
|
|
||||||
numberToBytesLE,
|
|
||||||
numberToVarBytesBE,
|
|
||||||
ensureBytes,
|
|
||||||
equalBytes,
|
|
||||||
utf8ToBytes,
|
|
||||||
bitLen,
|
|
||||||
bitGet,
|
|
||||||
bitSet,
|
|
||||||
bitMask,
|
|
||||||
} from "@noble/curves/abstract/utils";
|
|
||||||
export * from "./types.js";
|
export * from "./types.js";
|
||||||
|
export * from "./cid.js";
|
||||||
|
export * from "./encoding.js";
|
||||||
export * from "./keys.js";
|
export * from "./keys.js";
|
||||||
export * from "./download.js";
|
export { ed25519, sha512 };
|
||||||
export * from "./upload.js";
|
|
||||||
export * from "./portal.js";
|
|
||||||
export * from "./encryption.js";
|
|
||||||
export { ed25519, sha512, AppDb };
|
|
||||||
|
|
17
src/keys.ts
17
src/keys.ts
|
@ -1,28 +1,11 @@
|
||||||
import { blake3 } from "@noble/hashes/blake3";
|
import { blake3 } from "@noble/hashes/blake3";
|
||||||
import { concatBytes } from "@noble/hashes/utils";
|
import { concatBytes } from "@noble/hashes/utils";
|
||||||
import { hkdf } from "@noble/hashes/hkdf";
|
|
||||||
import { sha256 } from "@noble/hashes/sha256";
|
|
||||||
import { encodeEndian } from "@lumeweb/libs5";
|
|
||||||
|
|
||||||
export function deriveChildKey(
|
export function deriveChildKey(
|
||||||
parentKey: Uint8Array,
|
parentKey: Uint8Array,
|
||||||
tweak: string,
|
tweak: string,
|
||||||
): Uint8Array {
|
|
||||||
return hkdf(sha256, parentKey, undefined, tweak, 32);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deriveBlakeChildKey(
|
|
||||||
parentKey: Uint8Array,
|
|
||||||
tweak: string,
|
|
||||||
): Uint8Array {
|
): Uint8Array {
|
||||||
const tweakHash = blake3(tweak);
|
const tweakHash = blake3(tweak);
|
||||||
|
|
||||||
return blake3(concatBytes(parentKey, tweakHash));
|
return blake3(concatBytes(parentKey, tweakHash));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deriveBlakeChildKeyInt(
|
|
||||||
parentKey: Uint8Array,
|
|
||||||
tweak: number,
|
|
||||||
): Uint8Array {
|
|
||||||
return blake3(concatBytes(parentKey, encodeEndian(tweak, 32)));
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
// objAsString will try to return the provided object as a string. If the
|
||||||
|
// object is already a string, it will be returned without modification. If the
|
||||||
|
// object is an 'Error', the message of the error will be returned. If the object
|
||||||
|
// has a toString method, the toString method will be called and the result
|
||||||
|
// will be returned. If the object is null or undefined, a special string will
|
||||||
|
// be returned indicating that the undefined/null object cannot be converted to
|
||||||
|
// a string. In all other cases, JSON.stringify is used. If JSON.stringify
|
||||||
|
// throws an exception, a message "[could not provide object as string]" will
|
||||||
|
// be returned.
|
||||||
|
//
|
||||||
|
// NOTE: objAsString is intended to produce human readable output. It is lossy,
|
||||||
|
// and it is not intended to be used for serialization.
|
||||||
|
function objAsString(obj: any): string {
|
||||||
|
// Check for undefined input.
|
||||||
|
if (obj === undefined) {
|
||||||
|
return "[cannot convert undefined to string]";
|
||||||
|
}
|
||||||
|
if (obj === null) {
|
||||||
|
return "[cannot convert null to string]";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the error into a string.
|
||||||
|
if (typeof obj === "string") {
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the object is an error, and return the message of the error if
|
||||||
|
// so.
|
||||||
|
if (obj instanceof Error) {
|
||||||
|
return obj.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the object has a 'toString' method defined on it. To ensure
|
||||||
|
// that we don't crash or throw, check that the toString is a function, and
|
||||||
|
// also that the return value of toString is a string.
|
||||||
|
if (Object.prototype.hasOwnProperty.call(obj, "toString")) {
|
||||||
|
if (typeof obj.toString === "function") {
|
||||||
|
const str = obj.toString();
|
||||||
|
if (typeof str === "string") {
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the object does not have a custom toString, attempt to perform a
|
||||||
|
// JSON.stringify. We use a lot of bigints in libskynet, and calling
|
||||||
|
// JSON.stringify on an object with a bigint will cause a throw, so we add
|
||||||
|
// some custom handling to allow bigint objects to still be encoded.
|
||||||
|
try {
|
||||||
|
return JSON.stringify(obj, (_, v) => {
|
||||||
|
if (typeof v === "bigint") {
|
||||||
|
return v.toString();
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
});
|
||||||
|
} catch (err: any) {
|
||||||
|
if (err !== undefined && typeof err.message === "string") {
|
||||||
|
return `[stringify failed]: ${err.message}`;
|
||||||
|
}
|
||||||
|
return "[stringify failed]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { objAsString };
|
|
@ -0,0 +1,376 @@
|
||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
|
import { objAsString } from "./objAsString.js";
|
||||||
|
import { Err } from "./types.js";
|
||||||
|
|
||||||
|
// json_parse extracted from the json-bigint npm library
|
||||||
|
// regexpxs extracted from
|
||||||
|
// (c) BSD-3-Clause
|
||||||
|
// https://github.com/fastify/secure-json-parse/graphs/contributors and https://github.com/hapijs/bourne/graphs/contributors
|
||||||
|
const suspectProtoRx =
|
||||||
|
/(?:_|\\u005[Ff])(?:_|\\u005[Ff])(?:p|\\u0070)(?:r|\\u0072)(?:o|\\u006[Ff])(?:t|\\u0074)(?:o|\\u006[Ff])(?:_|\\u005[Ff])(?:_|\\u005[Ff])/;
|
||||||
|
const suspectConstructorRx =
|
||||||
|
/(?:c|\\u0063)(?:o|\\u006[Ff])(?:n|\\u006[Ee])(?:s|\\u0073)(?:t|\\u0074)(?:r|\\u0072)(?:u|\\u0075)(?:c|\\u0063)(?:t|\\u0074)(?:o|\\u006[Ff])(?:r|\\u0072)/;
|
||||||
|
let json_parse = function (options) {
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
// This is a function that can parse a JSON text, producing a JavaScript
|
||||||
|
// data structure. It is a simple, recursive descent parser. It does not use
|
||||||
|
// eval or regular expressions, so it can be used as a model for implementing
|
||||||
|
// a JSON parser in other languages.
|
||||||
|
|
||||||
|
// We are defining the function inside of another function to avoid creating
|
||||||
|
// global variables.
|
||||||
|
|
||||||
|
// Default options one can override by passing options to the parse()
|
||||||
|
let _options = {
|
||||||
|
strict: false, // not being strict means do not generate syntax errors for "duplicate key"
|
||||||
|
storeAsString: false, // toggles whether the values should be stored as BigNumber (default) or a string
|
||||||
|
alwaysParseAsBig: false, // toggles whether all numbers should be Big
|
||||||
|
protoAction: "error",
|
||||||
|
constructorAction: "error",
|
||||||
|
};
|
||||||
|
|
||||||
|
// If there are options, then use them to override the default _options
|
||||||
|
if (options !== undefined && options !== null) {
|
||||||
|
if (options.strict === true) {
|
||||||
|
_options.strict = true;
|
||||||
|
}
|
||||||
|
if (options.storeAsString === true) {
|
||||||
|
_options.storeAsString = true;
|
||||||
|
}
|
||||||
|
_options.alwaysParseAsBig = options.alwaysParseAsBig === true ? options.alwaysParseAsBig : false;
|
||||||
|
|
||||||
|
if (typeof options.constructorAction !== "undefined") {
|
||||||
|
if (
|
||||||
|
options.constructorAction === "error" ||
|
||||||
|
options.constructorAction === "ignore" ||
|
||||||
|
options.constructorAction === "preserve"
|
||||||
|
) {
|
||||||
|
_options.constructorAction = options.constructorAction;
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
`Incorrect value for constructorAction option, must be "error", "ignore" or undefined but passed ${options.constructorAction}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof options.protoAction !== "undefined") {
|
||||||
|
if (options.protoAction === "error" || options.protoAction === "ignore" || options.protoAction === "preserve") {
|
||||||
|
_options.protoAction = options.protoAction;
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
`Incorrect value for protoAction option, must be "error", "ignore" or undefined but passed ${options.protoAction}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let at, // The index of the current character
|
||||||
|
ch, // The current character
|
||||||
|
escapee = {
|
||||||
|
'"': '"',
|
||||||
|
"\\": "\\",
|
||||||
|
"/": "/",
|
||||||
|
b: "\b",
|
||||||
|
f: "\f",
|
||||||
|
n: "\n",
|
||||||
|
r: "\r",
|
||||||
|
t: "\t",
|
||||||
|
},
|
||||||
|
text,
|
||||||
|
error = function (m) {
|
||||||
|
// Call error when something is wrong.
|
||||||
|
|
||||||
|
throw {
|
||||||
|
name: "SyntaxError",
|
||||||
|
message: m,
|
||||||
|
at: at,
|
||||||
|
text: text,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
next = function (c) {
|
||||||
|
// If a c parameter is provided, verify that it matches the current character.
|
||||||
|
|
||||||
|
if (c && c !== ch) {
|
||||||
|
error("Expected '" + c + "' instead of '" + ch + "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the next character. When there are no more characters,
|
||||||
|
// return the empty string.
|
||||||
|
|
||||||
|
ch = text.charAt(at);
|
||||||
|
at += 1;
|
||||||
|
return ch;
|
||||||
|
},
|
||||||
|
number = function () {
|
||||||
|
// Parse a number value.
|
||||||
|
|
||||||
|
let number,
|
||||||
|
string = "";
|
||||||
|
|
||||||
|
if (ch === "-") {
|
||||||
|
string = "-";
|
||||||
|
next("-");
|
||||||
|
}
|
||||||
|
while (ch >= "0" && ch <= "9") {
|
||||||
|
string += ch;
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
if (ch === ".") {
|
||||||
|
string += ".";
|
||||||
|
while (next() && ch >= "0" && ch <= "9") {
|
||||||
|
string += ch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ch === "e" || ch === "E") {
|
||||||
|
string += ch;
|
||||||
|
next();
|
||||||
|
if (ch === "-" || ch === "+") {
|
||||||
|
string += ch;
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
while (ch >= "0" && ch <= "9") {
|
||||||
|
string += ch;
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
number = +string;
|
||||||
|
if (!isFinite(number)) {
|
||||||
|
error("Bad number");
|
||||||
|
} else {
|
||||||
|
if (Number.isSafeInteger(number)) return !_options.alwaysParseAsBig ? number : BigInt(number);
|
||||||
|
// Number with fractional part should be treated as number(double) including big integers in scientific notation, i.e 1.79e+308
|
||||||
|
else return _options.storeAsString ? string : /[.eE]/.test(string) ? number : BigInt(string);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
string = function () {
|
||||||
|
// Parse a string value.
|
||||||
|
|
||||||
|
let hex,
|
||||||
|
i,
|
||||||
|
string = "",
|
||||||
|
uffff;
|
||||||
|
|
||||||
|
// When parsing for string values, we must look for " and \ characters.
|
||||||
|
|
||||||
|
if (ch === '"') {
|
||||||
|
let startAt = at;
|
||||||
|
while (next()) {
|
||||||
|
if (ch === '"') {
|
||||||
|
if (at - 1 > startAt) string += text.substring(startAt, at - 1);
|
||||||
|
next();
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
if (ch === "\\") {
|
||||||
|
if (at - 1 > startAt) string += text.substring(startAt, at - 1);
|
||||||
|
next();
|
||||||
|
if (ch === "u") {
|
||||||
|
uffff = 0;
|
||||||
|
for (i = 0; i < 4; i += 1) {
|
||||||
|
hex = parseInt(next(), 16);
|
||||||
|
if (!isFinite(hex)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
uffff = uffff * 16 + hex;
|
||||||
|
}
|
||||||
|
string += String.fromCharCode(uffff);
|
||||||
|
} else if (typeof escapee[ch] === "string") {
|
||||||
|
string += escapee[ch];
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
startAt = at;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
error("Bad string");
|
||||||
|
},
|
||||||
|
white = function () {
|
||||||
|
// Skip whitespace.
|
||||||
|
|
||||||
|
while (ch && ch <= " ") {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
word = function () {
|
||||||
|
// true, false, or null.
|
||||||
|
|
||||||
|
switch (ch) {
|
||||||
|
case "t":
|
||||||
|
next("t");
|
||||||
|
next("r");
|
||||||
|
next("u");
|
||||||
|
next("e");
|
||||||
|
return true;
|
||||||
|
case "f":
|
||||||
|
next("f");
|
||||||
|
next("a");
|
||||||
|
next("l");
|
||||||
|
next("s");
|
||||||
|
next("e");
|
||||||
|
return false;
|
||||||
|
case "n":
|
||||||
|
next("n");
|
||||||
|
next("u");
|
||||||
|
next("l");
|
||||||
|
next("l");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
error("Unexpected '" + ch + "'");
|
||||||
|
},
|
||||||
|
value, // Place holder for the value function.
|
||||||
|
array = function () {
|
||||||
|
// Parse an array value.
|
||||||
|
|
||||||
|
let array = [];
|
||||||
|
|
||||||
|
if (ch === "[") {
|
||||||
|
next("[");
|
||||||
|
white();
|
||||||
|
if (ch === "]") {
|
||||||
|
next("]");
|
||||||
|
return array; // empty array
|
||||||
|
}
|
||||||
|
while (ch) {
|
||||||
|
array.push(value());
|
||||||
|
white();
|
||||||
|
if (ch === "]") {
|
||||||
|
next("]");
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
next(",");
|
||||||
|
white();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
error("Bad array");
|
||||||
|
},
|
||||||
|
object = function () {
|
||||||
|
// Parse an object value.
|
||||||
|
|
||||||
|
let key,
|
||||||
|
object = Object.create(null);
|
||||||
|
|
||||||
|
if (ch === "{") {
|
||||||
|
next("{");
|
||||||
|
white();
|
||||||
|
if (ch === "}") {
|
||||||
|
next("}");
|
||||||
|
return object; // empty object
|
||||||
|
}
|
||||||
|
while (ch) {
|
||||||
|
key = string();
|
||||||
|
white();
|
||||||
|
next(":");
|
||||||
|
if (_options.strict === true && Object.hasOwnProperty.call(object, key)) {
|
||||||
|
error('Duplicate key "' + key + '"');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (suspectProtoRx.test(key) === true) {
|
||||||
|
if (_options.protoAction === "error") {
|
||||||
|
error("Object contains forbidden prototype property");
|
||||||
|
} else if (_options.protoAction === "ignore") {
|
||||||
|
value();
|
||||||
|
} else {
|
||||||
|
object[key] = value();
|
||||||
|
}
|
||||||
|
} else if (suspectConstructorRx.test(key) === true) {
|
||||||
|
if (_options.constructorAction === "error") {
|
||||||
|
error("Object contains forbidden constructor property");
|
||||||
|
} else if (_options.constructorAction === "ignore") {
|
||||||
|
value();
|
||||||
|
} else {
|
||||||
|
object[key] = value();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
object[key] = value();
|
||||||
|
}
|
||||||
|
|
||||||
|
white();
|
||||||
|
if (ch === "}") {
|
||||||
|
next("}");
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
next(",");
|
||||||
|
white();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
error("Bad object");
|
||||||
|
};
|
||||||
|
|
||||||
|
value = function () {
|
||||||
|
// Parse a JSON value. It could be an object, an array, a string, a number,
|
||||||
|
// or a word.
|
||||||
|
|
||||||
|
white();
|
||||||
|
switch (ch) {
|
||||||
|
case "{":
|
||||||
|
return object();
|
||||||
|
case "[":
|
||||||
|
return array();
|
||||||
|
case '"':
|
||||||
|
return string();
|
||||||
|
case "-":
|
||||||
|
return number();
|
||||||
|
default:
|
||||||
|
return ch >= "0" && ch <= "9" ? number() : word();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Return the json_parse function. It will have access to all of the above
|
||||||
|
// functions and variables.
|
||||||
|
|
||||||
|
return function (source, reviver) {
|
||||||
|
let result;
|
||||||
|
|
||||||
|
text = source + "";
|
||||||
|
at = 0;
|
||||||
|
ch = " ";
|
||||||
|
result = value();
|
||||||
|
white();
|
||||||
|
if (ch) {
|
||||||
|
error("Syntax error");
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is a reviver function, we recursively walk the new structure,
|
||||||
|
// passing each name/value pair to the reviver function for possible
|
||||||
|
// transformation, starting with a temporary root object that holds the result
|
||||||
|
// in an empty key. If there is not a reviver function, we simply return the
|
||||||
|
// result.
|
||||||
|
|
||||||
|
return typeof reviver === "function"
|
||||||
|
? (function walk(holder, key) {
|
||||||
|
let v,
|
||||||
|
value = holder[key];
|
||||||
|
if (value && typeof value === "object") {
|
||||||
|
Object.keys(value).forEach(function (k) {
|
||||||
|
v = walk(value, k);
|
||||||
|
if (v !== undefined) {
|
||||||
|
value[k] = v;
|
||||||
|
} else {
|
||||||
|
delete value[k];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return reviver.call(holder, key, value);
|
||||||
|
})({ "": result }, "")
|
||||||
|
: result;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// parseJSON is a wrapper for JSONbig.parse that returns an error rather than
|
||||||
|
// throwing an error. JSONbig is an alternative to JSON.parse that decodes
|
||||||
|
// every number as a bigint. This is required when working with the skyd API
|
||||||
|
// because the skyd API uses 64 bit precision for all of its numbers, and
|
||||||
|
// therefore cannot be parsed losslessly by javascript. The skyd API is
|
||||||
|
// cryptographic, therefore full precision is required.
|
||||||
|
function parseJSON(json: string): [any, Err] {
|
||||||
|
try {
|
||||||
|
let obj = json_parse({ alwaysParseAsBig: true })(json);
|
||||||
|
return [obj, null];
|
||||||
|
} catch (err: any) {
|
||||||
|
return [{}, objAsString(err)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { parseJSON };
|
172
src/portal.ts
172
src/portal.ts
|
@ -1,172 +0,0 @@
|
||||||
import { Portal } from "#types.js";
|
|
||||||
import { Client } from "@lumeweb/libportal";
|
|
||||||
import { deriveChildKey } from "#keys.js";
|
|
||||||
import { bytesToHex } from "@noble/hashes/utils";
|
|
||||||
import COMMUNITY_PORTAL_LIST from "@lumeweb/community-portals";
|
|
||||||
import { createKeyPair } from "@lumeweb/libs5";
|
|
||||||
import type { KeyPairEd25519 } from "@lumeweb/libs5";
|
|
||||||
|
|
||||||
let activePortalMasterKey;
|
|
||||||
|
|
||||||
export const DEFAULT_PORTAL_LIST = COMMUNITY_PORTAL_LIST;
|
|
||||||
|
|
||||||
let ACTIVE_PORTALS = new Set<Client>();
|
|
||||||
|
|
||||||
type PortalSessionsStore = { [id: string]: string };
|
|
||||||
|
|
||||||
const PORTAL_ID = Symbol.for("PORTAL_ID");
|
|
||||||
const PORTAL_NAME = Symbol.for("PORTAL_NAME");
|
|
||||||
|
|
||||||
export function maybeInitDefaultPortals() {
|
|
||||||
if (!activePortalMasterKey) {
|
|
||||||
throw new Error("activePortalMasterKey not set");
|
|
||||||
}
|
|
||||||
|
|
||||||
let portalsToLoad = DEFAULT_PORTAL_LIST;
|
|
||||||
|
|
||||||
const savedPortals = loadSavedPortals();
|
|
||||||
|
|
||||||
if (savedPortals) {
|
|
||||||
portalsToLoad = savedPortals;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const portal of portalsToLoad) {
|
|
||||||
addActivePortal(initPortal(portal));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setActivePortalMasterKey(key: Uint8Array) {
|
|
||||||
activePortalMasterKey = key;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function generatePortalEmail(portal: Portal) {
|
|
||||||
const keyPair = generatePortalKeyPair(portal);
|
|
||||||
|
|
||||||
const userId = bytesToHex(keyPair.publicKey.slice(0, 12));
|
|
||||||
|
|
||||||
return `${userId}@example.com`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function generatePortalKeyPair(portal: Portal): KeyPairEd25519 {
|
|
||||||
const privateKey = deriveChildKey(
|
|
||||||
activePortalMasterKey,
|
|
||||||
`portal-account:${portal.id}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
return createKeyPair(privateKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getActivePortals(): Client[] {
|
|
||||||
return [...ACTIVE_PORTALS];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function addActivePortal(portal: Client) {
|
|
||||||
ACTIVE_PORTALS.add(portal);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function initPortal(portal: Portal): Client {
|
|
||||||
const sessions = getPortalSessions();
|
|
||||||
let jwt: string | null = null;
|
|
||||||
if (sessions) {
|
|
||||||
if (portal.id in sessions) {
|
|
||||||
jwt = sessions[portal.id];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const client = new Client({
|
|
||||||
email: generatePortalEmail(portal),
|
|
||||||
portalUrl: portal.url,
|
|
||||||
privateKey: generatePortalKeyPair(portal).extractBytes(),
|
|
||||||
jwt: jwt as string,
|
|
||||||
});
|
|
||||||
|
|
||||||
client[PORTAL_ID] = portal.id;
|
|
||||||
client[PORTAL_NAME] = portal.name;
|
|
||||||
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getPortalSessions() {
|
|
||||||
if (typeof globalThis.localStorage === "undefined") {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
let portalSessionsData = globalThis.localStorage.getItem("portal_sessions");
|
|
||||||
let portalSessions: PortalSessionsStore = {};
|
|
||||||
if (portalSessions) {
|
|
||||||
portalSessions = JSON.parse(
|
|
||||||
portalSessionsData as string,
|
|
||||||
) as PortalSessionsStore;
|
|
||||||
|
|
||||||
return portalSessions;
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setActivePortals(portals: Client[]) {
|
|
||||||
ACTIVE_PORTALS = new Set<Client>(portals);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function savePortals() {
|
|
||||||
const portals = [...ACTIVE_PORTALS.values()].map((item) => {
|
|
||||||
return {
|
|
||||||
id: item[PORTAL_ID],
|
|
||||||
name: item[PORTAL_NAME],
|
|
||||||
url: item.portalUrl,
|
|
||||||
} as Portal;
|
|
||||||
});
|
|
||||||
if (typeof globalThis.localStorage !== "undefined") {
|
|
||||||
globalThis.localStorage.setItem("portals", JSON.stringify(portals));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function savePortalSessions() {
|
|
||||||
const portalSessions = {};
|
|
||||||
|
|
||||||
for (const portal of ACTIVE_PORTALS) {
|
|
||||||
const jwt = portal.jwt;
|
|
||||||
if (jwt) {
|
|
||||||
portalSessions[portal[PORTAL_ID]] = jwt;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof globalThis.localStorage !== "undefined") {
|
|
||||||
globalThis.localStorage.setItem(
|
|
||||||
"portal_sessions",
|
|
||||||
JSON.stringify(portalSessions),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function loadSavedPortals(): Portal[] | null {
|
|
||||||
let portals: string | null = null;
|
|
||||||
|
|
||||||
if (typeof globalThis.localStorage !== "undefined") {
|
|
||||||
portals = globalThis.localStorage.getItem("portals");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!portals) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return JSON.parse(portals);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function loginActivePortals() {
|
|
||||||
const activePortals = getActivePortals();
|
|
||||||
|
|
||||||
if (!activePortals.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const portal of activePortals) {
|
|
||||||
if (!(await portal.isLoggedIn())) {
|
|
||||||
try {
|
|
||||||
await portal.register();
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
await portal.login();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { addContextToErr } from "./err.js";
|
||||||
|
import { objAsString } from "./objAsString.js";
|
||||||
|
import { Err } from "./types.js";
|
||||||
|
|
||||||
|
// jsonStringify is a replacement for JSON.stringify that returns an error
|
||||||
|
// rather than throwing.
|
||||||
|
function jsonStringify(obj: any): [string, Err] {
|
||||||
|
try {
|
||||||
|
const str = JSON.stringify(obj, (_, v) => {
|
||||||
|
if (typeof v === "bigint") {
|
||||||
|
return Number(v);
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
});
|
||||||
|
return [str, null];
|
||||||
|
} catch (err) {
|
||||||
|
return ["", addContextToErr(objAsString(err), "unable to stringify object")];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { jsonStringify };
|
77
src/types.ts
77
src/types.ts
|
@ -1,9 +1,82 @@
|
||||||
|
// DataFn can take any object as input and has no return value. The input is
|
||||||
|
// allowed to be undefined.
|
||||||
|
type DataFn = (data?: any) => void;
|
||||||
|
|
||||||
|
// Err is an error type that is either a string or a null. If the value is
|
||||||
|
// null, that indicates that there was no error. If the value is a string, it
|
||||||
|
// indicates that there was an error and the string contains the error message.
|
||||||
|
//
|
||||||
|
// The skynet libraries prefer this error type to the standard Error type
|
||||||
|
// because many times skynet libraries need to pass errors over postMessage,
|
||||||
|
// and the 'Error' type is not able to be sent over postMessage.
|
||||||
|
type Err = string | null;
|
||||||
|
|
||||||
|
// ErrFn must take an error message as input. The input is not allowed to be
|
||||||
|
// undefined or null, there must be an error.
|
||||||
|
type ErrFn = (errMsg: string) => void;
|
||||||
|
|
||||||
|
// ErrTuple is a type that pairs a 'data' field with an 'err' field. Skynet
|
||||||
|
// libraries typically prefer returning ErrTuples to throwing or rejecting,
|
||||||
|
// because it allows upstream code to avoid the try/catch/throw pattern. Though
|
||||||
|
// the pattern is much celebrated in javascript, it encourages relaxed error
|
||||||
|
// handling, and often makes error handling much more difficult because the try
|
||||||
|
// and the catch are in different scopes.
|
||||||
|
//
|
||||||
|
// Most of the Skynet core libraries do not have any `throws` anywhere in their
|
||||||
|
// API.
|
||||||
|
//
|
||||||
|
// Typically, an ErrTuple will have only one field filled out. If data is
|
||||||
|
// returned, the err should be 'null'. If an error is returned, the data field
|
||||||
|
// should generally be empty. Callers are expected to check the error before
|
||||||
|
// they access any part of the data field.
|
||||||
|
type ErrTuple = [data: any, err: Err];
|
||||||
|
|
||||||
|
// KernelAuthStatus is the structure of a message that gets sent by the kernel
|
||||||
|
// containing its auth status. Auth occurs in 5 stages.
|
||||||
|
//
|
||||||
|
// Stage 0; no auth updates
|
||||||
|
// Stage 1: bootloader is loaded, user is not yet logged in
|
||||||
|
// Stage 2: bootloader is loaded, user is logged in
|
||||||
|
// Stage 3: kernel is loaded, user is logged in
|
||||||
|
// Stage 4: kernel is loaded, user is logging out (refresh iminent)
|
||||||
|
//
|
||||||
|
// 'kernelLoaded' is initially set to "not yet" and will be updated when the
|
||||||
|
// kernel has loaded. If it is set to "success", it means the kernel loaded
|
||||||
|
// without issues. If it is set to anything else, it means that there was an
|
||||||
|
// error, and the new value is the error.
|
||||||
|
//
|
||||||
|
// 'kernelLoaded' will not be changed until 'loginComplete' has been set to
|
||||||
|
// true. 'loginComplete' can be set to true immediately if the user is already
|
||||||
|
// logged in.
|
||||||
|
//
|
||||||
|
// 'logoutComplete' can be set to 'true' at any point, which indicates that the
|
||||||
|
// auth cycle needs to reset.
|
||||||
|
interface KernelAuthStatus {
|
||||||
|
loginComplete: boolean;
|
||||||
|
kernelLoaded: "not yet" | "success" | string;
|
||||||
|
logoutComplete: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
interface Portal {
|
interface Portal {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
url: string;
|
url: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NO_PORTALS_ERROR = new Error("no active portals");
|
// RequestOverrideResponse defines the type that the kernel returns as a
|
||||||
|
// response to a requestOverride call.
|
||||||
|
interface RequestOverrideResponse {
|
||||||
|
override: boolean;
|
||||||
|
headers?: any; // TODO: I don't know how to do an array of types.
|
||||||
|
body?: Uint8Array;
|
||||||
|
}
|
||||||
|
|
||||||
export { Portal, NO_PORTALS_ERROR };
|
export {
|
||||||
|
DataFn,
|
||||||
|
ErrFn,
|
||||||
|
Err,
|
||||||
|
ErrTuple,
|
||||||
|
KernelAuthStatus,
|
||||||
|
RequestOverrideResponse,
|
||||||
|
Portal,
|
||||||
|
};
|
||||||
|
|
|
@ -1,39 +0,0 @@
|
||||||
import { NO_PORTALS_ERROR } from "#types.js";
|
|
||||||
import { getActivePortals } from "#portal.js";
|
|
||||||
import { CID } from "@lumeweb/libs5";
|
|
||||||
|
|
||||||
export async function uploadObject(
|
|
||||||
file:
|
|
||||||
| ReadableStream<Uint8Array>
|
|
||||||
| import("stream").Readable
|
|
||||||
| Uint8Array
|
|
||||||
| Blob,
|
|
||||||
size?: bigint,
|
|
||||||
): Promise<CID> {
|
|
||||||
const activePortals = getActivePortals();
|
|
||||||
|
|
||||||
if (!activePortals.length) {
|
|
||||||
throw NO_PORTALS_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const portal of activePortals) {
|
|
||||||
if (!(await portal.isLoggedIn())) {
|
|
||||||
try {
|
|
||||||
await portal.register();
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
await portal.login();
|
|
||||||
}
|
|
||||||
|
|
||||||
let upload;
|
|
||||||
try {
|
|
||||||
upload = await portal.uploadFile(file as any, size);
|
|
||||||
} catch {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
return upload;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw NO_PORTALS_ERROR;
|
|
||||||
}
|
|
Loading…
Reference in New Issue