Compare commits

..

No commits in common. "4ddfa970aaa323f99a19af7801d1efaab5907253" and "7efd901b978db3023b6512dfe089aa8d4b307e65" have entirely different histories.

18 changed files with 5232 additions and 1402 deletions

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,7 @@
"description": "Lume Web is your decentralized gateway into the web3 internet, the web owned and controlled by its users",
"manifest_version": 2,
"name": "Lume Web",
"version": "0.3.0.3",
"version": "0.3.0",
"homepage_url": "https://lumeweb.com",
"icons": {
"48": "icon.png",
@ -24,7 +24,7 @@
"content_scripts": [
{
"matches": [
"http://kernel.lume/"
"http://kernel.skynet/"
],
"js": [
"bootloader.js"

View File

@ -11,13 +11,12 @@ esbuild.buildSync({
global: "self",
},
inject: ["./polyfill.js"],
external: ["node:stream"],
});
esbuild.buildSync({
entryPoints: ["src/main/bootloader.ts"],
outfile: "dist/bootloader.js",
format: "iife",
format: "esm",
bundle: true,
legalComments: "external",
// minify: true
@ -28,14 +27,14 @@ esbuild.buildSync({
esbuild.buildSync({
entryPoints: ["src/main/bridge.ts"],
outfile: "dist/bridge.js",
format: "iife",
format: "esm",
bundle: true,
legalComments: "external",
// minify: true
define: {
global: "self",
},
}); /*
});
esbuild.buildSync({
entryPoints: ["src/main/crypto.ts"],
outfile: "dist/crypto.js",
@ -59,4 +58,3 @@ esbuild.buildSync({
global: "window",
}
});
*/

View File

@ -15,56 +15,45 @@
"author": "David Vorick",
"license": "MIT",
"devDependencies": {
"@helia/unixfs": "^1.2.1",
"@lumeweb/kernel-dns-client": "https://github.com/LumeWeb/kernel-dns-client.git",
"@lumeweb/webextension-polyfill": "https://github.com/LumeWeb/webextension-polyfill.git",
"@rollup/plugin-node-resolve": "^13.3.0",
"@types/ejs": "^3.1.2",
"@types/ejs": "^3.1.1",
"@types/read": "^0.0.29",
"@types/webextension-polyfill": "^0.9.2",
"@typescript-eslint/eslint-plugin": "^5.57.1",
"@types/webextension-polyfill": "^0.9.0",
"@typescript-eslint/eslint-plugin": "^5.18.0",
"browserify-fs": "^1.0.0",
"cpy-cli": "^4.2.0",
"esbuild": "^0.14.54",
"eslint": "^8.38.0",
"cpy-cli": "^4.1.0",
"esbuild": "^0.14.51",
"eslint": "^8.13.0",
"events": "^3.3.0",
"path-browserify": "^1.0.1",
"prettier": "^2.8.7",
"prettier": "^2.6.2",
"process": "^0.11.10",
"rimraf": "^3.0.2",
"rollup": "^2.79.1",
"rollup": "^2.75.6",
"stream": "^0.0.2",
"typescript": "^4.9.5",
"util": "^0.12.5",
"typescript": "^4.6.3",
"util": "^0.12.4",
"webextension-polyfill": "^0.9.0"
},
"dependencies": {
"@lumeweb/kernel-dns-client": "git+https://git.lumeweb.com/LumeWeb/kernel-dns-client.git",
"@lumeweb/kernel-ipfs-client": "git+https://git.lumeweb.com/LumeWeb/kernel-ipfs-client.git",
"@lumeweb/kernel-peer-discovery-client": "git+https://git.lumeweb.com/LumeWeb/kernel-peer-discovery-client.git",
"@lumeweb/kernel-swarm-client": "git+https://git.lumeweb.com/LumeWeb/kernel-swarm-client.git",
"@lumeweb/libresolver": "git+https://git.lumeweb.com/LumeWeb/libresolver.git",
"@lumeweb/tld-enum": "git+https://git.lumeweb.com/LumeWeb/list-of-top-level-domains.git",
"@lumeweb/webextension-polyfill": "git+https://git.lumeweb.com/LumeWeb/webextension-polyfill.git",
"@peculiar/webcrypto": "^1.4.3",
"@siaweb/libweb": "git+https://git.lumeweb.com/LumeWeb/libsiaweb.git",
"buffer": "^6.0.3",
"file-type": "^18.2.1",
"@lumeweb/kernel-dht-client": "https://github.com/LumeWeb/kernel-dht-client.git",
"@lumeweb/kernel-ipfs-client": "https://github.com/LumeWeb/kernel-ipfs-client.git",
"@lumeweb/libresolver": "https://github.com/LumeWeb/libresolver.git",
"@lumeweb/tld-enum": "https://github.com/LumeWeb/list-of-top-level-domains.git",
"@peculiar/webcrypto": "^1.4.0",
"dexie": "^3.2.2",
"ejs": "^3.1.8",
"is-ipfs": "^6.0.2",
"libkernel": "github:LumeWeb/libextension",
"libkernel": "https://github.com/LumeWeb/libextension.git",
"libskynet": "^0.0.62",
"multiformats": "^11.0.2",
"node-cache": "^5.1.2",
"p-defer": "^4.0.0"
"node-cache": "^5.1.2"
},
"browser": {
"crypto": "crypto-browserify",
"fs": "browserify-fs",
"libkmodule": false,
"path": "path-browserify",
"node:buffer": "buffer"
},
"pnpm": {
"overrides": {
"libkernel": "github:LumeWeb/libextension"
}
"fs": "browserify-fs",
"crypto": "crypto-browserify"
}
}

View File

@ -1,11 +0,0 @@
import { createClient as createDnsClient } from "@lumeweb/kernel-dns-client";
import { createClient as createIpfsClient } from "@lumeweb/kernel-ipfs-client";
import { createClient as createSwarmClient } from "@lumeweb/kernel-swarm-client";
import { createClient as createPeerDiscoveryClient } from "@lumeweb/kernel-peer-discovery-client";
const dnsClient = createDnsClient();
const ipfsClient = createIpfsClient();
const swarmClient = createSwarmClient();
const peerDiscoveryClient = createPeerDiscoveryClient();
export { dnsClient, ipfsClient, swarmClient, peerDiscoveryClient };

View File

@ -52,6 +52,10 @@ export default abstract class BaseProvider {
const originalUrl = new URL(details.url);
const hostname = normalizeDomain(originalUrl.hostname);
if (typeof getAuthStatus === "undefined") {
debugger;
}
if (getAuthStatus().loginComplete !== true) {
return false;
}

View File

@ -7,22 +7,86 @@ import {
OnRequestDetailsType,
StreamFilter,
} from "../types.js";
import { getRelayProxies } from "../util.js";
import { ipfsPath, ipnsPath, path as checkPath } from "is-ipfs";
import { createClient } from "@lumeweb/kernel-ipfs-client";
import { getRelayProxies, streamToArray } from "../util.js";
import { ipfsPath, ipnsPath, path } from "is-ipfs";
import {
fetchIpfs,
fetchIpns,
statIpfs,
statIpns,
} from "@lumeweb/kernel-ipfs-client";
import ejs from "ejs";
import { cacheDb } from "../databases.js";
import { DNS_RECORD_TYPE, DNSResult } from "@lumeweb/libresolver";
import RequestStream from "../requestStream.js";
import { UnixFSStats } from "@helia/unixfs";
import * as path from "path";
import { CID } from "multiformats/cid";
import { fileTypeFromBuffer } from "file-type";
import extToMimes from "../mimes.js";
import NodeCache from "node-cache";
import ContentFilterRegistry from "../contentFilterRegistry.js";
const INDEX_HTML_FILES = ["index.html", "index.htm", "index.shtml"];
const DIRECTORY_TEMPLATE = ejs.compile(`
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title><%= path %></title>
<style></style>
</head>
<body>
<div id="header" class="row">
<div class="col-xs-2">
<div id="logo" class="ipfs-logo"></div>
</div>
</div>
<br>
<div class="col-xs-12">
<div class="panel panel-default">
<div class="panel-heading">
<strong>Index of <%= path %></strong>
</div>
<table class="table table-striped">
<tbody>
<tr>
<td class="narrow">
<div class="ipfs-icon ipfs-_blank">&nbsp;</div>
</td>
<td class="padding">
<a href="<%= parentHref %>">..</a>
</td>
<td></td>
</tr>
<% links.forEach(function (link) { %>
<tr>
<td><div class="ipfs-icon ipfs-_blank">&nbsp;</div></td>
<td><a href="<%= link.link %>"><%= link.name %></a></t>
<td><%= link.size %></td>
</td>
</tr>
<% }) %>
</tbody>
</table>
</div>
</div>
</body>
</html>`);
interface StatFileResponse {
exists: boolean;
contentType: string | null;
error: any;
directory: boolean;
files: StatFileSubfile[];
timeout: boolean;
size: number;
}
interface StatFileSubfile {
name: string;
size: number;
}
const MAX_CACHE_SIZE = 1024 * 1024 * 1024 * 50;
export default class IpfsProvider extends BaseProvider {
private _ipnsCache = new NodeCache({ stdTTL: 60 * 60 * 24 });
private _client = createClient();
async shouldHandleRequest(
details: OnBeforeRequestDetailsType
): Promise<boolean> {
@ -33,17 +97,17 @@ export default class IpfsProvider extends BaseProvider {
if (!dnsResult) {
return false;
}
let contentRecords = (dnsResult as DNSResult).records.map(
(item: { value: string }) =>
"/" + item.value.replace("://", "/").replace(/^\+/, "/")
(item) => "/" + item.value.replace("://", "/").replace(/^\+/, "/")
);
contentRecords = contentRecords.filter((item) => checkPath(item));
contentRecords = contentRecords.filter((item) => path(item));
if (!contentRecords.length) {
return false;
}
this.setData(details, "cid", contentRecords.shift());
this.setData(details, "hash", contentRecords.shift());
return true;
}
@ -67,121 +131,114 @@ export default class IpfsProvider extends BaseProvider {
): Promise<BlockingResponse | boolean> {
let urlObj = new URL(details.url);
let urlPath = urlObj.pathname;
let cid = this.getData(details, "cid");
let hash = this.getData(details, "hash");
let resp: StatFileResponse | null = null;
let fetchMethod: typeof fetchIpfs | typeof fetchIpns;
let err;
let stat: UnixFSStats | null = null;
const parsedPath = path.parse(urlPath);
let contentType: string;
let contentSize = 0;
let cachedPage: { contentType: string; data: Blob } | null = null;
try {
if (ipnsPath(cid)) {
const cidHash = cid.replace("/ipns/", "");
if (this._ipnsCache.has(cidHash)) {
cid = this._ipnsCache.get(cidHash);
// @ts-ignore
cachedPage = await cacheDb.items.where("url").equals(details.url).first();
} catch {}
if (!cachedPage) {
try {
if (ipfsPath(hash)) {
hash = hash.replace("/ipfs/", "");
resp = await statIpfs(hash.replace("/ipfs/", ""), urlPath);
fetchMethod = fetchIpfs;
} else if (ipnsPath(hash)) {
hash = hash.replace("/ipns/", "");
resp = await statIpns(hash.replace("/ipns/", ""), urlPath);
fetchMethod = fetchIpns;
} else {
cid = await this._client.ipns(cidHash);
this._ipnsCache.set(cidHash, cid);
err = "invalid content";
}
cid = `/ipfs/${cid}`;
}
if (ipfsPath(cid)) {
cid = CID.parse(cid.replace("/ipfs/", "")).toV1().toString();
stat = await this._client.stat(cid);
}
} catch (e) {
} catch (e: any) {
err = (e as Error).message;
}
if (err) {
err = "404";
contentType = resp?.contentType as string;
if (contentType?.includes(";")) {
contentType = contentType?.split(";").shift() as string;
}
contentSize = resp?.size as number;
} else {
contentType = cachedPage.contentType;
contentSize = cachedPage.data.size;
}
if (!err && stat?.type === "directory") {
if (!parsedPath.base.length || !parsedPath.ext.length) {
let found = false;
for (const indexFile of ["index.html", "index.htm"]) {
try {
const subPath = path.join(urlPath, indexFile);
await this._client.stat(cid, {
path: subPath,
});
urlPath = subPath;
found = true;
break;
} catch {}
}
if (!found) {
if (resp) {
if (!resp.exists) {
err = "404";
}
if (resp.directory) {
contentType = "text/html";
}
}
const reqStream = new RequestStream(details);
this.setData(details, "contentType", contentType);
const isSmallFile = contentSize <= MAX_CACHE_SIZE;
const reqStream = new RequestStream(
details,
isSmallFile && ContentFilterRegistry.hasFilters(contentType)
? ContentFilterRegistry.filter(contentType)
: undefined
);
reqStream.start();
if (err) {
reqStream.close();
return {};
}
const streamWriter = reqStream.stream.writable.getWriter();
const reader = await this._client.cat(cid, { path: urlPath });
const provider = this;
if (cachedPage) {
(
cachedPage?.data.stream() as unknown as ReadableStream<Uint8Array>
).pipeThrough(reqStream.stream);
return {};
}
if (resp?.directory) {
let indexFiles =
resp?.files.filter((item) => INDEX_HTML_FILES.includes(item.name)) ||
[];
let streaming = (async function () {
let bufferRead = 0;
const fileTypeBufferLength = 4100;
const mimeBuffer = [];
let checkMime = false;
if (indexFiles.length > 0) {
urlPath += `/${indexFiles[0].name}`;
}
}
try {
if (isSmallFile) {
streamToArray(reqStream.readableStream).then((data: Uint8Array) => {
// @ts-ignore
for await (const chunk of reader.iterable()) {
streamWriter.write(chunk);
if (bufferRead < fileTypeBufferLength) {
if (chunk.length >= fileTypeBufferLength) {
mimeBuffer.push(chunk.slice(0, fileTypeBufferLength));
bufferRead += fileTypeBufferLength;
} else {
mimeBuffer.push(chunk);
bufferRead += chunk.length;
return cacheDb.items.put({
url: details.url,
contentType,
data: new Blob([data.buffer], { type: contentType }),
timestamp: Date.now(),
});
});
}
if (bufferRead >= fileTypeBufferLength) {
checkMime = true;
}
} else {
checkMime = true;
}
}
} catch (e) {
const streamWriter = reqStream.stream.writable.getWriter();
// @ts-ignore
fetchMethod?.(hash, urlPath, (data: Buffer) => {
streamWriter.write(data);
})
.then(() => {
streamWriter.releaseLock();
return reqStream.close();
})
.catch((e: any) => {
streamWriter.releaseLock();
reqStream.close();
return;
}
if (checkMime) {
const mime = await fileTypeFromBuffer(
mimeBuffer.reduce((acc, val) => {
return new Uint8Array([...acc, ...val]);
}, new Uint8Array())
);
if (mime) {
provider.setData(details, "contentType", mime.mime);
}
if (!mime) {
const ext = path.parse(urlPath).ext.replace(".", "");
if (extToMimes.has(ext)) {
provider.setData(details, "contentType", extToMimes.get(ext));
}
}
}
streamWriter.releaseLock();
reqStream.close();
})();
});
return {};
}
@ -190,6 +247,7 @@ export default class IpfsProvider extends BaseProvider {
details: OnHeadersReceivedDetailsType
): Promise<BlockingResponse | boolean> {
let headers = [];
headers.push({
name: "Content-Type",
value: this.getData(details, "contentType"),

View File

@ -0,0 +1,117 @@
import BaseProvider from "./baseProvider.js";
import {
BlockingResponse,
OnBeforeRequestDetailsType,
OnHeadersReceivedDetailsType,
OnRequestDetailsType,
} from "../types.js";
import { validSkylink } from "libskynet";
import { downloadSkylink, getRelayProxies } from "../util.js";
import browser from "@lumeweb/webextension-polyfill";
import { DNS_RECORD_TYPE, DNSResult } from "@lumeweb/libresolver";
export default class SkynetProvider extends BaseProvider {
async shouldHandleRequest(
details: OnBeforeRequestDetailsType
): Promise<boolean> {
let dnsResult: DNSResult | boolean | string = await this.resolveDns(
details,
[DNS_RECORD_TYPE.CONTENT, DNS_RECORD_TYPE.TEXT]
);
if (!dnsResult) {
return false;
}
let contentRecords = (dnsResult as DNSResult).records
.map((item) => {
item.value = item.value.replace("sia://", "");
return item;
})
.filter((item) => {
try {
return validSkylink(item.value);
} catch (e) {
return false;
}
});
if (!contentRecords.length) {
return false;
}
this.setData(details, "hash", contentRecords.shift()?.value);
return true;
}
async handleProxy(details: OnRequestDetailsType): Promise<any> {
return getRelayProxies();
}
async handleRequest(
details: OnBeforeRequestDetailsType
): Promise<BlockingResponse | boolean> {
const hash = this.getData(details, "hash");
let urlObj = new URL(details.url);
let path = urlObj.pathname;
let fileData: any, err;
if (urlObj.protocol == "https") {
urlObj.protocol = "http";
return { redirectUrl: urlObj.toString() };
}
try {
[fileData, err] = await downloadSkylink(hash, path);
} catch (e: any) {
debugger;
this.setData(details, "error", (e as Error).message);
return {};
}
if (err) {
this.setData(details, "error", err);
return {};
}
this.setData(details, "headers", fileData.response?.headers);
const filter = browser.webRequest.filterResponseData(details.requestId);
filter.ondata = () => {};
filter.onstop = () => {
fileData.response.arrayBuffer().then((data: any) => {
filter.write(data);
filter.close();
});
};
return true;
}
async handleHeaders(
details: OnHeadersReceivedDetailsType
): Promise<BlockingResponse | boolean> {
const err = this.getData(details, "error");
let headers: Headers = this.getData(details, "headers") as Headers;
if (err) {
return {
responseHeaders: [
{
name: "Status-Code",
value: err == "404" ? "404" : "400",
},
{
name: "Content-Type",
value: "text/html; charset=utf8",
},
],
};
}
return {
responseHeaders: Array.from(headers).map((item: string[]) => {
return { name: item[0], value: item[1] };
}),
};
}
}

View File

@ -2,12 +2,20 @@ import browser from "@lumeweb/webextension-polyfill";
import type WebEngine from "./webEngine.js";
import type { Menus, Tabs } from "./types.js";
import IpfsProvider from "./contentProviders/ipfsProvider.js";
import { cacheDb } from "./databases.js";
export default function setup(engine: WebEngine) {
browser.menus.create({
title: "Clear Cache",
id: "clear-cache",
onclick: async (info: Menus.OnClickData, tab: Tabs.Tab) => {
// @ts-ignore
await cacheDb.items
.where("url")
.startsWithIgnoreCase(
`http://${new URL(info.pageUrl as string).hostname}`
)
.delete();
browser.tabs.reload(tab.id);
},
});

7
src/databases.ts Normal file
View File

@ -0,0 +1,7 @@
import Dexie from "dexie";
export const cacheDb = new Dexie("LumeWebIFSCache");
cacheDb.version(1).stores({
items: `url,contentType,data,timestamp`,
});

View File

@ -1,12 +1,16 @@
import NodeCache from "node-cache";
import {
ready as dnsReady,
resolve as resolveDns,
} from "@lumeweb/kernel-dns-client";
import {
DNS_RECORD_TYPE,
DNSRecord,
DNSResult,
ResolverOptions,
} from "@lumeweb/libresolver";
import { blake2b, bufToHex } from "libskynet/dist";
import { getDnsSetupDefer } from "./main/vars.js";
import { dnsClient } from "./clients.js";
import { blake2b, bufToHex, Err } from "libskynet/dist";
import { getDnsSetupPromise } from "./main/vars.js";
const cache = new NodeCache({ stdTTL: 60 });
@ -24,11 +28,11 @@ export async function resolve(
return cache.get(cacheId) as DNSResult;
}
await getDnsSetupDefer().promise;
await getDnsSetupPromise();
let res;
try {
res = await dnsClient.resolve(domain, options, bypassCache);
res = await resolveDns(domain, options, bypassCache);
} catch (e: any) {
return e as Error;
}

View File

@ -1,9 +1,11 @@
import tldEnum from "@lumeweb/tld-enum";
import WebEngine from "../webEngine.js";
import InternalProvider from "../contentProviders/internalProvider.js";
import SkynetProvider from "../contentProviders/skynetProvider.js";
import ServerProvider from "../contentProviders/serverProvider.js";
import { init, kernelLoaded } from "libkernel";
import { init } from "libkernel";
import IpfsProvider from "../contentProviders/ipfsProvider.js";
import { ready as dnsReady } from "@lumeweb/kernel-dns-client";
import {
addQuery,
getAuthStatusResolve,
@ -30,19 +32,10 @@ import {
setKernelIframe,
setOpenPort,
setTimer,
getDnsSetupPromise,
getDnsSetupDefer,
} from "./vars.js";
// @ts-ignore
import browser from "@lumeweb/webextension-polyfill";
import setupContextMenus from "../contextMenu.js";
import { callModule } from "libkernel";
import {
dnsClient,
ipfsClient,
peerDiscoveryClient,
swarmClient,
} from "../clients.js";
function logLargeObjects() {
let queriesLen = Object.keys(getQueries()).length;
@ -56,7 +49,6 @@ function logLargeObjects() {
setTimer(getTimer() * 1.25);
setTimeout(logLargeObjects, getTimer());
}
setTimeout(logLargeObjects, getTimer());
export function queryKernel(query: any): Promise<any> {
@ -83,7 +75,6 @@ export function queryKernel(query: any): Promise<any> {
});
});
}
function handleKernelMessage(event: MessageEvent) {
let data = event.data.data;
@ -183,10 +174,8 @@ function handleBridgeMessage(
});
data["domain"] = domain;
}
getKernelIframe().contentWindow!.postMessage(data, "http://kernel.lume");
}
function bridgeListener(port: any) {
let portNonce = getPortsNonce();
increasePortsNonce();
@ -217,49 +206,35 @@ async function boot() {
const engine = new WebEngine();
engine.registerContentProvider(new InternalProvider(engine));
engine.registerContentProvider(new SkynetProvider(engine));
engine.registerContentProvider(new IpfsProvider(engine));
engine.registerContentProvider(new ServerProvider(engine));
setKernelIframe(document.createElement("iframe"));
getKernelIframe().src = "http://kernel.lume";
await new Promise((resolve) => {
getKernelIframe().onload = () => {
init().then(resolve);
};
getKernelIframe().onload = init;
document.body.appendChild(getKernelIframe());
});
// @ts-ignore
window.callModule = callModule;
await kernelLoaded();
await swarmClient.addRelay(
"fd35779a2dcae738308098e8f6702e25c282a52cce972ff2f96bcc50d5043c99"
);
await peerDiscoveryClient.register(
"_AEPtjxDCq3H4nmLLV7-P0L3D_d_Aude4i9O9S498dXcFw"
);
await ipfsClient.ready();
setupContextMenus(engine);
dnsSetup();
await getDnsSetupDefer().promise;
console.log("ready");
setDnsSetupPromise(dnsSetup());
}
async function dnsSetup() {
const resolvers = [
"_B0tpRWWzAf77qfhiRMx1EGTDURht_2V9VsUmMqIzcpW4Q", // ens
// "vAMl33T1TusZqZmJl9mlWJCbYm_Lu1TPjE3aSl2ZFHE_yg", // hns
"AQBXtVkPDbZ5Qmjl8dzJ0siSYaFcS3XbDZHapxmZCLfwfg", // icann
"AQAI3TbarrXRxWtrb_5XO-gMYg-UsjVAChue5JEoqywbAw", // eip137
"AQD0s0wZNpZCVg_iO96E6Ff66WxGa2CZst_DCYR_DoQPxw", // solana
"AQDtYcJGbquAHA-idtZ-gPOlNBgEVeCZtZUtsyL_J5ZiUA", // algorand
"AQDkqoCzCR6s5MP_k6Ee9QWfEwaH5-7XleCKFL1CdxExxQ", // avax
"AQC7ALr-OtkFT7qZby2BdMMNbTRXHNMGlpV6r96b35Z79Q", // evmos
"AQAmQoZLu1DqIiZaRWRpomvMarQ8Uc3kdHJQBo0r-9uYtg", // handshake
];
for (const resolver of resolvers) {
await dnsClient.registerResolver(resolver);
await callModule(resolver, "register");
}
getDnsSetupDefer().resolve();
await dnsReady();
}
boot();

View File

@ -1,14 +1,28 @@
import {
addContextToErr,
b64ToBuf,
bufToHex,
bufToStr,
computeRegistrySignature,
defaultPortalList,
deriveChildSeed,
deriveRegistryEntryID,
downloadSkylink,
Ed25519Keypair,
entryIDToSkylink,
Err,
hexToBuf,
progressiveFetch,
progressiveFetchResult,
taggedRegistryEntryKeys,
objAsString,
} from "@siaweb/libweb";
verifyRegistryWriteResponse,
} from "libskynet";
declare var browser: any; // tsc
var browser: any; // tsc
const defaultKernelLink = "RAC1FocOb2bQw6uwjN0AX__MJ8F-h71F5kvIgQPTKo7fQA";
const defaultKernelResolverLink =
"AQDJDoXMJiiEMBxXodQvUV89qtQHsnXWyV1ViQ9M1pMjUg";
document.title = "kernel.lume";
let header = document.createElement("h1");
@ -240,44 +254,143 @@ function downloadKernel(
kernelSkylink: string
): Promise<[kernelCode: string, err: Err]> {
return new Promise((resolve) => {
fetch(`https://web3portal.com/${kernelSkylink}`).then((result) => {
if (result.status === 404) {
resolve(["", result.status.toString()]);
downloadSkylink(kernelSkylink).then(([fileData, err]) => {
if (err === "404") {
resolve(["", err]);
return;
}
if (!result.ok) {
resolve(["", result.statusText]);
return;
}
result
.blob()
.then((blob) => {
return blob.arrayBuffer();
})
.then((data) => {
let [kernelCode, errBBTS] = bufToStr(data);
if (errBBTS !== null) {
if (err !== null) {
resolve([
"",
addContextToErr(null, "unable to decode the default kernel"),
addContextToErr(err, "unable to download the default kernel"),
]);
return;
}
let [kernelCode, errBBTS] = bufToStr(fileData);
if (errBBTS !== null) {
resolve([
"",
addContextToErr(err, "unable to decode the default kernel"),
]);
return;
}
resolve([kernelCode, null]);
});
});
});
}
function downloadDefaultKernel(): Promise<[kernelCode: string, err: Err]> {
return downloadKernel(defaultKernelLink);
return downloadKernel(defaultKernelResolverLink);
}
async function loadKernel() {
let [kernelCode, err] = await downloadDefaultKernel();
function setUserKernelAsDefault(keypair: Ed25519Keypair, dataKey: Uint8Array) {
log(
"user kernel not found, setting user kernel to " + defaultKernelResolverLink
);
let [defaultKernelSkylink, err64] = b64ToBuf(defaultKernelResolverLink);
if (err64 !== null) {
log("unable to convert default kernel link to a Uint8Array");
return;
}
let [sig, errCRS] = computeRegistrySignature(
keypair.secretKey,
dataKey,
defaultKernelSkylink,
0n
);
if (errCRS !== null) {
log(
addContextToErr(
errCRS,
"unable to compute registry signature to set user kernel"
)
);
return;
}
let dataKeyHex = bufToHex(dataKey);
let endpoint = "/skynet/registry";
let postBody = {
publickey: {
algorithm: "ed25519",
key: Array.from(keypair.publicKey),
},
datakey: dataKeyHex,
revision: 0,
data: Array.from(defaultKernelSkylink),
signature: Array.from(sig),
};
let fetchOpts = {
method: "post",
body: JSON.stringify(postBody),
};
progressiveFetch(
endpoint,
fetchOpts,
defaultPortalList,
verifyRegistryWriteResponse
).then((result: progressiveFetchResult) => {
if (result.success !== true) {
log("unable to update the user kernel registry entry\n", result.logs);
return;
}
log(
"successfully updated the user kernel registry entry to the default kernel"
);
});
}
function downloadUserKernel(): Promise<[kernelCode: string, err: Err]> {
return new Promise((resolve) => {
let kernelEntrySeed = deriveChildSeed(userSeed, "userPreferredKernel2");
let [keypair, dataKey, errTREK] = taggedRegistryEntryKeys(
kernelEntrySeed,
"user kernel"
);
if (errTREK !== null) {
resolve([
"",
addContextToErr(errTREK, "unable to create user kernel registry keys"),
]);
return;
}
let [entryID, errREID] = deriveRegistryEntryID(keypair.publicKey, dataKey);
if (errREID !== null) {
resolve([
"",
addContextToErr(errREID, "unable to derive registry entry id"),
]);
return;
}
let userKernelSkylink = entryIDToSkylink(entryID);
downloadKernel(userKernelSkylink).then(([kernelCode, err]) => {
if (err === "404") {
downloadDefaultKernel().then(([defaultCode, errDefault]) => {
if (errDefault === null) {
setUserKernelAsDefault(keypair, dataKey);
}
resolve([defaultCode, errDefault]);
return;
});
return;
}
log("found user kernel, using: " + userKernelSkylink);
resolve([kernelCode, err]);
});
});
}
function loadKernel() {
downloadUserKernel().then(([kernelCode, err]) => {
if (err !== null) {
let extErr = addContextToErr(err, "unable to download kernel");
kernelLoaded = extErr;
@ -302,6 +415,7 @@ async function loadKernel() {
sendAuthUpdate();
return;
}
});
}
let loginComplete = false;
@ -322,9 +436,9 @@ function sendAuthUpdate() {
}
sendAuthUpdate();
var userSeed: Uint8Array;
let userSeed: Uint8Array;
function checkForLoadKernel() {
let userSeedString = window.localStorage.getItem("v1-key");
let userSeedString = window.localStorage.getItem("v1-seed");
if (userSeedString === null) {
sendAuthUpdate();
return;

View File

@ -130,12 +130,6 @@ function handleMessage(event: MessageEvent) {
// Everything else just gets ignored.
}
window.addEventListener("message", handleMessage);
window.addEventListener("message", (event) => {
port.postMessage({
method: "log1",
data: [event.data, event.origin],
});
});
port.postMessage({
method: "bridgeLoaded",
});

View File

@ -1,5 +1,4 @@
import type { DataFn, KernelAuthStatus } from "libskynet";
import defer, { DeferredPromise } from "p-defer";
export let queriesNonce = 1;
export let queries: any = {};
@ -18,7 +17,7 @@ let blockForBridge = new Promise((resolve) => {
bridgeLoadedResolve = resolve;
});
let kernelFrame: HTMLIFrameElement;
let blockForDnsSetup = defer();
let blockForDnsSetup: Promise<void>;
export function getAuthStatusKnown() {
return authStatusKnown;
@ -96,7 +95,10 @@ export function setKernelIframe(iframe: HTMLIFrameElement) {
kernelFrame = iframe;
}
export function getDnsSetupDefer(): DeferredPromise<any> {
export function setDnsSetupPromise(p: Promise<void>) {
blockForDnsSetup = p;
}
export function getDnsSetupPromise(): Promise<void> {
return blockForDnsSetup;
}
export function getAuthStatusResolve(): DataFn {

View File

@ -1,10 +0,0 @@
const extToMimes = new Map(
Object.entries({
html: "text/html",
xhtml: "application/xhtml+xml",
xml: "application/xml",
})
);
Object.freeze(extToMimes);
export default extToMimes;

View File

@ -1,8 +1,21 @@
import { defaultPortalList } from "libskynet";
import {
addContextToErr,
b64ToBuf,
defaultPortalList,
Err,
objAsString,
progressiveFetch,
progressiveFetchResult,
validSkylink,
verifyDownloadResponse,
} from "libskynet";
import { DHT } from "@lumeweb/kernel-dht-client";
defaultPortalList.unshift("https://web3portal.com");
defaultPortalList.pop();
const relayDht = new DHT();
export function isIp(ip: string) {
return /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(
ip
@ -20,15 +33,12 @@ export function normalizeDomain(domain: string): string {
}
export async function getRelayProxies() {
//let relays: string[] = await relayDht.getRelayServers();
let proxies = [
{ type: "http", host: "localhost", port: 25252 },
{ type: "http", host: "web3portal.com", port: 80 },
];
/*
let relays: string[] = await relayDht.getRelayServers();
let proxies = [{ type: "http", host: "localhost", port: 25252 }];
for (const relay of relays) {
proxies.push({ type: "http", host: new URL(relay).hostname, port: 25252 });
}*/
}
return proxies;
}
@ -36,6 +46,9 @@ export async function getRelayProxies() {
export const requestProxies = [
{ type: "http", host: "localhost", port: 25252 },
{ type: "http", host: "web3portal.com", port: 80 },
{ type: "http", host: "siasky.net", port: 80 },
{ type: "http", host: "skynetfree.net", port: 80 },
{ type: "http", host: "skynetpro.net", port: 80 },
];
export function getTld(hostname: string): string {
@ -43,6 +56,80 @@ export function getTld(hostname: string): string {
? hostname.split(".")[hostname.split(".").length - 1]
: hostname;
}
export type FileDataType = {
err: null;
response: Response | null;
fileData: Uint8Array;
};
export function downloadSkylink(
skylink: string,
path?: string
): Promise<[data: FileDataType, err: Err]> {
return new Promise((resolve) => {
// Get the Uint8Array of the input skylink.
let [u8Link, errBTB] = b64ToBuf(skylink);
if (errBTB !== null) {
resolve([{} as any, addContextToErr(errBTB, "unable to decode skylink")]);
return;
}
if (!validSkylink(u8Link)) {
resolve([{} as any, "skylink appears to be invalid"]);
return;
}
// Prepare the download call.
let endpoint = "/" + skylink;
if (path) {
endpoint += path;
}
let fileDataPtr: FileDataType = {
fileData: new Uint8Array(0),
err: null,
response: null,
};
let verifyFunction = function (response: Response): Promise<Err> {
return verifyDownloadResponse(response, u8Link, fileDataPtr);
};
// Perform the download call.
progressiveFetch(endpoint, null, defaultPortalList, () =>
Promise.resolve(null)
).then((result: progressiveFetchResult) => {
// Return an error if the call failed.
if (result.success !== true) {
// Check for a 404.
for (let i = 0; i < result.responsesFailed.length; i++) {
if (result.responsesFailed[i].status === 404) {
resolve([{} as any, "404"]);
return;
}
}
// Error is not a 404, return the logs as the error.
let err = objAsString(result.logs);
resolve([
{} as any,
addContextToErr(err, "unable to complete download"),
]);
return;
}
// Check if the portal is honest but the download is corrupt.
if (fileDataPtr.err !== null) {
resolve([
{} as any,
addContextToErr(fileDataPtr.err, "download is corrupt"),
]);
return;
}
fileDataPtr.response = result.response;
resolve([fileDataPtr, null]);
});
});
}
export async function* iterateStream(
stream: ReadableStream<any>
): AsyncGenerator<Uint8Array> {

2933
yarn.lock Normal file

File diff suppressed because it is too large Load Diff