Compare commits

...

27 Commits

Author SHA1 Message Date
semantic-release-bot 73842ec263 chore(release): 0.2.0-develop.51 [skip ci]
# [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](2c6b8438e1))
* remove export ([9bc7fda](9bc7fda440))
* use toRegistryEntry of CID ([841e453](841e453a8c))

### Features

* add deriveBlakeChildKeyInt ([282c431](282c431021))
* implement appdb, known as hidden db in s5 ([2894e63](2894e63aa4))
2023-09-08 12:27:09 +00:00
Derrick Hammer ce62654b85
refactor: move to use KeyPairEd25519 and createKeyPair 2023-09-08 08:25:49 -04:00
Derrick Hammer 060e092fb2
dep: update libportal 2023-09-08 08:25:21 -04:00
Derrick Hammer 794036c667
dep: update libportal 2023-09-08 07:59:17 -04:00
Derrick Hammer 9bc7fda440
fix: remove export 2023-09-08 07:06:11 -04:00
Derrick Hammer 841e453a8c
fix: use toRegistryEntry of CID 2023-09-08 06:59:05 -04:00
Derrick Hammer e30747945c
refactor: remove function version of cid api 2023-09-08 06:58:44 -04:00
Derrick Hammer d349e454e5
Merge remote-tracking branch 'origin/develop' into develop 2023-09-08 06:33:17 -04:00
Derrick Hammer 2894e63aa4
feat: implement appdb, known as hidden db in s5 2023-09-08 06:33:00 -04:00
Derrick Hammer cc247a350e
dep: update libportal 2023-09-08 06:22:27 -04:00
Derrick Hammer aef5a40a43
refactor: remove exports 2023-09-08 05:43:00 -04:00
Derrick Hammer 40eedf2817
refactor: remove encoding functions 2023-09-08 05:42:50 -04:00
Derrick Hammer 282c431021
feat: add deriveBlakeChildKeyInt 2023-09-08 05:38:22 -04:00
Derrick Hammer 68cb638086
dep: update libportal 2023-09-08 05:37:56 -04:00
Derrick Hammer 6ef11e9a27
refactor: move away from the tuple pattern 2023-09-08 05:37:09 -04:00
Derrick Hammer 3f1dc5304c
refactor: move errTracker to libkernel 2023-09-07 19:39:35 -04:00
Derrick Hammer db50583dc2
refactor: remove err 2023-09-07 19:39:14 -04:00
Derrick Hammer 2c6b8438e1
fix: move NO_PORTALS_ERROR to types and re-import 2023-09-07 19:38:56 -04:00
Derrick Hammer 623bbf3235
refactor: remove exports 2023-09-07 19:33:52 -04:00
Derrick Hammer 84a34496cd
refactor: move types to libkernel 2023-09-07 19:33:07 -04:00
Derrick Hammer c882bb341b
dep: update libportal, add @noble/ciphers 2023-09-07 19:32:06 -04:00
Derrick Hammer aca55ba230
refactor: move to libkernel 2023-09-07 19:31:03 -04:00
Derrick Hammer d83d4892cc
refactor: custom json stringify not needed 2023-09-07 19:30:51 -04:00
Derrick Hammer 885aaadd4f
refactor: custom json parse not needed 2023-09-07 19:30:34 -04:00
Derrick Hammer a8f2eb666b
refactor: move away from the tuple pattern 2023-09-07 19:29:49 -04:00
Derrick Hammer 2300b07419
refactor: move away from the tuple pattern 2023-09-07 19:15:33 -04:00
Derrick Hammer 6a285b54f1
dep: update libportal 2023-09-04 09:32:19 -04:00
18 changed files with 368 additions and 997 deletions

View File

@ -1,3 +1,18 @@
# [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)

41
npm-shrinkwrap.json generated
View File

@ -1,16 +1,17 @@
{
"name": "@lumeweb/libweb",
"version": "0.2.0-develop.50",
"version": "0.2.0-develop.51",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@lumeweb/libweb",
"version": "0.2.0-develop.50",
"version": "0.2.0-develop.51",
"dependencies": {
"@lumeweb/community-portals": "^0.1.0-develop.6",
"@lumeweb/libportal": "0.2.0-develop.24",
"@lumeweb/libportal": "0.2.0-develop.31",
"@lumeweb/node-library-preset": "0.2.7",
"@noble/ciphers": "^0.3.0",
"@noble/curves": "^1.1.0",
"@noble/hashes": "^1.3.1",
"binconv": "^0.2.0"
@ -1677,11 +1678,11 @@
}
},
"node_modules/@lumeweb/libportal": {
"version": "0.2.0-develop.24",
"resolved": "https://registry.npmjs.org/@lumeweb/libportal/-/libportal-0.2.0-develop.24.tgz",
"integrity": "sha512-EEsHjgmQOkzpU16mXXB6+O4Xv/4aQ+ApcWaShlCJQnHi4EFxuPDPJP6uIoBPhIcgXMm05ZF1YSs10ognAEXlcw==",
"version": "0.2.0-develop.31",
"resolved": "https://registry.npmjs.org/@lumeweb/libportal/-/libportal-0.2.0-develop.31.tgz",
"integrity": "sha512-6/8AGJTnDsnsTOaFDokmt4cDIX9cIvA61PcKWxLZ6sMgUz8LM7HV6f/FT/RgZBI9dBcXuXlRyBVcwZze47QOIw==",
"dependencies": {
"@lumeweb/libs5": "^0.1.0-develop.45",
"@lumeweb/libs5": "^0.1.0-develop.52",
"@noble/curves": "^1.1.0",
"@noble/hashes": "^1.3.1",
"detect-node": "^2.1.0",
@ -1694,9 +1695,9 @@
}
},
"node_modules/@lumeweb/libs5": {
"version": "0.1.0-develop.45",
"resolved": "https://registry.npmjs.org/@lumeweb/libs5/-/libs5-0.1.0-develop.45.tgz",
"integrity": "sha512-D2q8WhCrus/IWcvOmNE1BLPFCdwUzz0wp4hXzYggIRggui0tOBGKpLgo80ZLnjH9LUqaevAkwi9emJ6DrdDgyg==",
"version": "0.1.0-develop.52",
"resolved": "https://registry.npmjs.org/@lumeweb/libs5/-/libs5-0.1.0-develop.52.tgz",
"integrity": "sha512-COQQCQJrhJRApAty1jkhhM4SIjRZviWgZrnZtjLq+7WABYiDHpSXaaiXuLTt7k5Nras1NqEAaMNOa/DHi8R67Q==",
"dependencies": {
"@noble/curves": "^1.1.0",
"@noble/hashes": "^1.3.1",
@ -1708,9 +1709,9 @@
}
},
"node_modules/@lumeweb/libs5/node_modules/multiformats": {
"version": "12.1.0",
"resolved": "https://registry.npmjs.org/multiformats/-/multiformats-12.1.0.tgz",
"integrity": "sha512-/qTOKKnU8nwcVURjRcS+UN0QYgdS5BPZzY10Aiciu2SqncyCVMGV8KtD83EBFmsuJDsSEmT4sGvzcTkCoMw0sQ==",
"version": "12.1.1",
"resolved": "https://registry.npmjs.org/multiformats/-/multiformats-12.1.1.tgz",
"integrity": "sha512-GBSToTmri2vJYs8wqcZQ8kB21dCaeTOzHTIAlr8J06C1eL6UbzqURXFZ5Fl0EYm9GAFz1IlYY8SxGOs9G9NJRg==",
"engines": {
"node": ">=16.0.0",
"npm": ">=7.0.0"
@ -1756,6 +1757,14 @@
"semantic-release": "^21.0.5"
}
},
"node_modules/@noble/ciphers": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.3.0.tgz",
"integrity": "sha512-ldbrnOjmNRwFdXcTM6uXDcxpMIFrbzAWNnpBPp4oTJTFF0XByGD6vf45WrehZGXRQTRVV+Zm8YP+EgEf+e4cWA==",
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@noble/curves": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.1.0.tgz",
@ -18028,9 +18037,9 @@
}
},
"node_modules/ws": {
"version": "8.13.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
"integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
"version": "8.14.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.14.0.tgz",
"integrity": "sha512-WR0RJE9Ehsio6U4TuM+LmunEsjQ5ncHlw4sn9ihD6RoJKZrVyH9FWV3dmnwu8B2aNib1OvG2X6adUCyFpQyWcg==",
"engines": {
"node": ">=10.0.0"
},

View File

@ -1,6 +1,6 @@
{
"name": "@lumeweb/libweb",
"version": "0.2.0-develop.50",
"version": "0.2.0-develop.51",
"main": "lib/index.js",
"type": "module",
"repository": {
@ -22,8 +22,9 @@
},
"dependencies": {
"@lumeweb/community-portals": "^0.1.0-develop.6",
"@lumeweb/libportal": "0.2.0-develop.24",
"@lumeweb/libportal": "0.2.0-develop.31",
"@lumeweb/node-library-preset": "0.2.7",
"@noble/ciphers": "^0.3.0",
"@noble/curves": "^1.1.0",
"@noble/hashes": "^1.3.1",
"binconv": "^0.2.0"

176
src/appDb.ts Normal file
View File

@ -0,0 +1,176 @@
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 };

View File

@ -1,145 +0,0 @@
import { ErrTuple } from "#types.js";
import type { CID } from "@lumeweb/libportal";
import {
decodeCid as decodeCidPortal,
encodeCid as encodeCidPortal,
} from "@lumeweb/libportal";
import { addContextToErr } from "#err.js";
import { CID_HASH_TYPES, CID_TYPES, REGISTRY_TYPES } from "@lumeweb/libs5";
import { concatBytes } from "@noble/hashes/utils";
export function encodeCid(
hash: Uint8Array,
size: bigint,
type?: number,
hashType?: number,
raw?: boolean,
): ErrTuple<string | Uint8Array>;
export function encodeCid(
hash: string,
size: bigint,
type?: number,
hashType?: number,
raw?: boolean,
): ErrTuple<string | Uint8Array>;
export function encodeCid(
hash: CID,
size?: bigint,
type?: number,
hashType?: number,
raw?: boolean,
): ErrTuple<string | Uint8Array>;
export function encodeCid(
hash: any,
size?: bigint,
type?: number,
hashType?: number,
raw: boolean = false,
): ErrTuple<string | Uint8Array> {
if (typeof hash !== "string" && !(hash instanceof Uint8Array)) {
size = hash.size;
type = type ?? hash.type;
hashType = hashType ?? hash.hashType;
hash = hash.hash;
}
try {
return [encodeCidPortal(hash, size, type, hashType, raw), null];
} catch (e) {
return ["", addContextToErr(e as Error, "failed to encode cid")];
}
}
export function decodeCid(cid: string | Uint8Array): ErrTuple<CID> {
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 function encodeRegistryCid(
hash: Uint8Array,
size?: bigint,
type?: number,
hashType?: number,
raw?: boolean,
): ErrTuple<string | Uint8Array>;
export function encodeRegistryCid(
hash: string,
size?: bigint,
type?: number,
hashType?: number,
raw?: boolean,
): ErrTuple<string | Uint8Array>;
export function encodeRegistryCid(
hash: any,
size = BigInt(0),
type = CID_TYPES.RESOLVER,
hashType = CID_HASH_TYPES.ED25519,
raw: boolean = false,
): ErrTuple<string | Uint8Array> {
if (hash instanceof Uint8Array) {
if (Object.values(CID_HASH_TYPES).includes(hash[0])) {
hash = hash.slice(1);
}
}
return encodeCid(hash, size, type, hashType, raw);
}
export function encodeRegistryValue(
cid: CID | string,
type: number = REGISTRY_TYPES.CID,
hashType = CID_HASH_TYPES.BLAKE3,
): ErrTuple<Uint8Array> {
if (typeof cid === "string") {
let err;
[cid, err] = decodeCid(cid);
if (err) {
return [new Uint8Array(), err];
}
}
const [ret, err] = encodeCid(cid.hash, cid.size, cid.type, hashType, true);
if (err) {
return [new Uint8Array(), err];
}
return [concatBytes(Uint8Array.from([type]), ret as Uint8Array), null];
}
export function decodeRegistryValue(hash: Uint8Array): ErrTuple<CID> {
if (!Object.values(REGISTRY_TYPES).includes(hash[0])) {
return [null, "invalid registry type"];
}
hash = hash.slice(1);
return decodeCid(hash);
}
export function decodeRegistryCid(cid: string | Uint8Array): ErrTuple<CID> {
const [ret, err] = decodeCid(cid);
if (err) {
return [null, err];
}
if (ret.type !== CID_TYPES.RESOLVER) {
return [null, "not a valid registry cid"];
}
return [ret, null];
}
export type { CID } from "@lumeweb/libportal";

View File

@ -1,5 +1,4 @@
import { getActivePortals } from "#portal.js";
import { ErrTuple } from "#types.js";
import { decodeCid, getVerifiableStream } from "@lumeweb/libportal";
import {
readableStreamToUint8Array,
@ -7,14 +6,13 @@ import {
} from "binconv";
import { equalBytes } from "@noble/curves/abstract/utils";
import { blake3 } from "@noble/hashes/blake3";
import { NO_PORTALS_ERROR } from "#types.js";
const NO_PORTALS_ERROR = [null, "no active portals"] as ErrTuple;
export async function downloadObject(cid: string): Promise<ErrTuple> {
export async function downloadObject(cid: string): Promise<ReadableStream> {
const activePortals = getActivePortals();
if (!activePortals.length) {
return NO_PORTALS_ERROR;
throw NO_PORTALS_ERROR;
}
for (const portal of activePortals) {
@ -35,20 +33,19 @@ export async function downloadObject(cid: string): Promise<ErrTuple> {
continue;
}
return [
await getVerifiableStream(decodeCid(cid).hash, proof, stream),
null,
];
return await getVerifiableStream(decodeCid(cid).hash, proof, stream);
}
return NO_PORTALS_ERROR;
throw NO_PORTALS_ERROR;
}
export async function downloadSmallObject(cid: string): Promise<ErrTuple> {
export async function downloadSmallObject(
cid: string,
): Promise<ReadableStream<Uint8Array>> {
const activePortals = getActivePortals();
if (!activePortals.length) {
return NO_PORTALS_ERROR;
throw NO_PORTALS_ERROR;
}
for (const portal of activePortals) {
@ -72,11 +69,11 @@ export async function downloadSmallObject(cid: string): Promise<ErrTuple> {
const data = await readableStreamToUint8Array(stream);
if (!equalBytes(blake3(data), CID.hash)) {
return [null, "cid verification failed"];
throw new Error("cid verification failed");
}
return [uint8ArrayToReadableStream(data), null];
return uint8ArrayToReadableStream(data);
}
return NO_PORTALS_ERROR;
throw NO_PORTALS_ERROR;
}

View File

@ -1,131 +0,0 @@
import { addContextToErr } from "./err.js";
import { Err } from "./types.js";
import { bytesToHex } from "@noble/hashes/utils";
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 bytesToHex(buf);
}
// 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,
bufToB64,
bufToStr,
decodeU64,
encodeU64,
bufToHex,
hexToBuf,
};

109
src/encryption.ts Normal file
View File

@ -0,0 +1,109 @@
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,
};

View File

@ -1,19 +0,0 @@
import { objAsString } from "./objAsString.js";
import { ErrTuple } from "#types.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 };
export const NO_PORTALS_ERROR = [null, "no active portals"] as ErrTuple;

View File

@ -1,101 +0,0 @@
// 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,6 +1,8 @@
import { ed25519 } from "@noble/curves/ed25519";
import { sha512 } from "@noble/hashes/sha512";
import AppDb from "#appDb.js";
export {
bytesToHex,
hexToBytes,
@ -24,16 +26,10 @@ export {
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 { ed25519, sha512 };
export * from "./encryption.js";
export { ed25519, sha512, AppDb };

View File

@ -2,6 +2,7 @@ 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,
@ -18,3 +19,10 @@ export function deriveBlakeChildKey(
return blake3(concatBytes(parentKey, tweakHash));
}
export function deriveBlakeChildKeyInt(
parentKey: Uint8Array,
tweak: number,
): Uint8Array {
return blake3(concatBytes(parentKey, encodeEndian(tweak, 32)));
}

View File

@ -1,64 +0,0 @@
// 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 };

View File

@ -1,376 +0,0 @@
// @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,10 +1,10 @@
import { ErrTuple, KeyPair, Portal } from "#types.js";
import { Portal } from "#types.js";
import { Client } from "@lumeweb/libportal";
import { deriveChildKey } from "#keys.js";
import { ed25519 } from "@noble/curves/ed25519";
import { bytesToHex } from "@noble/hashes/utils";
import COMMUNITY_PORTAL_LIST from "@lumeweb/community-portals";
import { NO_PORTALS_ERROR } from "#err.js";
import { createKeyPair } from "@lumeweb/libs5";
import type { KeyPairEd25519 } from "@lumeweb/libs5";
let activePortalMasterKey;
@ -17,9 +17,9 @@ type PortalSessionsStore = { [id: string]: string };
const PORTAL_ID = Symbol.for("PORTAL_ID");
const PORTAL_NAME = Symbol.for("PORTAL_NAME");
export function maybeInitDefaultPortals(): ErrTuple {
export function maybeInitDefaultPortals() {
if (!activePortalMasterKey) {
return [null, "activePortalMasterKey not set"];
throw new Error("activePortalMasterKey not set");
}
let portalsToLoad = DEFAULT_PORTAL_LIST;
@ -33,8 +33,6 @@ export function maybeInitDefaultPortals(): ErrTuple {
for (const portal of portalsToLoad) {
addActivePortal(initPortal(portal));
}
return [null, null];
}
export function setActivePortalMasterKey(key: Uint8Array) {
@ -49,16 +47,13 @@ export function generatePortalEmail(portal: Portal) {
return `${userId}@example.com`;
}
export function generatePortalKeyPair(portal: Portal): KeyPair {
export function generatePortalKeyPair(portal: Portal): KeyPairEd25519 {
const privateKey = deriveChildKey(
activePortalMasterKey,
`portal-account:${portal.id}`,
);
return {
privateKey,
publicKey: ed25519.getPublicKey(privateKey),
};
return createKeyPair(privateKey);
}
export function getActivePortals(): Client[] {
@ -81,7 +76,7 @@ export function initPortal(portal: Portal): Client {
const client = new Client({
email: generatePortalEmail(portal),
portalUrl: portal.url,
privateKey: generatePortalKeyPair(portal).privateKey,
privateKey: generatePortalKeyPair(portal).extractBytes(),
jwt: jwt as string,
});

View File

@ -1,21 +0,0 @@
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,87 +1,9 @@
// 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<T = any> = [data: T, 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;
}
// 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;
}
const NO_PORTALS_ERROR = new Error("no active portals");
export interface KeyPair {
publicKey: Uint8Array;
privateKey: Uint8Array;
}
export {
DataFn,
ErrFn,
Err,
ErrTuple,
KernelAuthStatus,
RequestOverrideResponse,
Portal,
};
export { Portal, NO_PORTALS_ERROR };

View File

@ -1,6 +1,6 @@
import { ErrTuple } from "#types.js";
import { NO_PORTALS_ERROR } from "#types.js";
import { getActivePortals } from "#portal.js";
import { NO_PORTALS_ERROR } from "#err.js";
import { CID } from "@lumeweb/libs5";
export async function uploadObject(
file:
@ -9,11 +9,11 @@ export async function uploadObject(
| Uint8Array
| Blob,
size?: bigint,
): Promise<ErrTuple> {
): Promise<CID> {
const activePortals = getActivePortals();
if (!activePortals.length) {
return NO_PORTALS_ERROR;
throw NO_PORTALS_ERROR;
}
for (const portal of activePortals) {
@ -32,8 +32,8 @@ export async function uploadObject(
continue;
}
return [upload, null];
return upload;
}
return NO_PORTALS_ERROR;
throw NO_PORTALS_ERROR;
}