Compare commits

..

1 Commits

Author SHA1 Message Date
semantic-release-bot 50ba311259 chore(release): 0.2.0 [skip ci]
# [0.2.0](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.1.3...v0.2.0) (2023-06-21)

### Features

* add deriveChildKey function ([d7cdaaf](d7cdaaf316))
2023-06-21 08:33:25 +00:00
22 changed files with 3391 additions and 2174 deletions

45
.circleci/config.yml Normal file
View File

@ -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

View File

@ -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

View File

@ -1,3 +1,18 @@
{
"preset": "@lumeweb/node-library-preset"
"preset": "presetter-preset-strict",
"config": {
"tsconfig": {
"compilerOptions": {
"lib": [
"ES2021"
]
}
},
"prettier": {
"singleQuote": false
}
},
"variable": {
"source": "src"
}
}

22
.releaserc Normal file
View File

@ -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
},
]
}

View File

@ -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-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))
# [0.2.0](https://git.lumeweb.com/LumeWeb/libweb/compare/v0.1.3...v0.2.0) (2023-06-21)
### Features
* 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)

3687
npm-shrinkwrap.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "@lumeweb/libweb",
"version": "0.2.0-develop.61",
"version": "0.2.0",
"main": "lib/index.js",
"type": "module",
"repository": {
@ -21,13 +21,9 @@
"semantic-release": "semantic-release"
},
"dependencies": {
"@lumeweb/community-portals": "^0.1.0-develop.6",
"@lumeweb/libportal": "0.2.0-develop.41",
"@lumeweb/node-library-preset": "0.2.7",
"@noble/ciphers": "^0.3.0",
"@lumeweb/libportal": "^0.1.0",
"@noble/curves": "^1.1.0",
"@noble/hashes": "^1.3.1",
"binconv": "^0.2.0"
"@noble/hashes": "^1.3.1"
},
"publishConfig": {
"access": "public"

View File

@ -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 };

35
src/cid.ts Normal file
View File

@ -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";

View File

@ -1,83 +1 @@
import { getActivePortals } from "#portal.js";
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;
}
export function downloadObject(cid: string) {}

130
src/encoding.ts Normal file
View File

@ -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,
};

View File

@ -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,
};

16
src/err.ts Normal file
View File

@ -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 };

101
src/errTracker.ts Normal file
View File

@ -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 };

View File

@ -1,35 +1,13 @@
import { ed25519 } from "@noble/curves/ed25519";
import { sha512 } from "@noble/hashes/sha512";
import AppDb from "#appDb.js";
export {
bytesToHex,
hexToBytes,
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 "./err.js";
export * from "./errTracker.js";
export * from "./objAsString.js";
export * from "./parse.js";
export * from "./stringifyJSON.js";
export * from "./types.js";
export * from "./cid.js";
export * from "./encoding.js";
export * from "./keys.js";
export * from "./download.js";
export * from "./upload.js";
export * from "./portal.js";
export * from "./encryption.js";
export { ed25519, sha512, AppDb };
export { ed25519, sha512 };

View File

@ -1,28 +1,11 @@
import { blake3 } from "@noble/hashes/blake3";
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(
parentKey: Uint8Array,
tweak: string,
): Uint8Array {
return hkdf(sha256, parentKey, undefined, tweak, 32);
}
export function deriveBlakeChildKey(
parentKey: Uint8Array,
tweak: string,
): Uint8Array {
const tweakHash = blake3(tweak);
return blake3(concatBytes(parentKey, tweakHash));
}
export function deriveBlakeChildKeyInt(
parentKey: Uint8Array,
tweak: number,
): Uint8Array {
return blake3(concatBytes(parentKey, encodeEndian(tweak, 32)));
}

64
src/objAsString.ts Normal file
View File

@ -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 };

376
src/parse.ts Normal file
View File

@ -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 };

View File

@ -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();
}
}
}

21
src/stringifyJSON.ts Normal file
View File

@ -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 };

View File

@ -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 {
id: string;
name: 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,
};

View File

@ -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;
}