Compare commits
No commits in common. "develop" and "master" have entirely different histories.
|
@ -1,68 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
#browser-container {
|
|
||||||
width: 100%;
|
|
||||||
height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
#address-bar-container {
|
|
||||||
padding: 10px;
|
|
||||||
background-color: #f1f1f1;
|
|
||||||
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.1);
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#address-bar {
|
|
||||||
flex-grow: 1;
|
|
||||||
padding: 10px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 3px;
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#go-button {
|
|
||||||
padding: 10px 20px;
|
|
||||||
border: none;
|
|
||||||
background-color: #007bff;
|
|
||||||
color: white;
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#web-content {
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
#booting {
|
|
||||||
margin: 1em;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<title></title>
|
|
||||||
<script type="application/javascript" src="./index.js"></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<div id="browser-container">
|
|
||||||
<div id="address-bar-container">
|
|
||||||
<span id="booting">Booting</span>
|
|
||||||
<input type="text" id="address-bar" placeholder="Enter URL..." disabled>
|
|
||||||
<button id="go-button" disabled>Go</button>
|
|
||||||
</div>
|
|
||||||
<iframe id="web-content" src="about:blank" frameborder="0" style="width: 100%; height: 100%;"></iframe>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
31
package.json
31
package.json
|
@ -1,31 +0,0 @@
|
||||||
{
|
|
||||||
"name": "browser-webapp",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"type": "module",
|
|
||||||
"devDependencies": {
|
|
||||||
"@lumeweb/presetter-kernel-module-preset": "^0.1.0-develop.1",
|
|
||||||
"presetter": "*"
|
|
||||||
},
|
|
||||||
"readme": "ERROR: No README data found!",
|
|
||||||
"_id": "browser-webapp@0.1.0",
|
|
||||||
"scripts": {
|
|
||||||
"prepare": "presetter bootstrap",
|
|
||||||
"build": "run build",
|
|
||||||
"semantic-release": "semantic-release"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@helia/unixfs": "^1.4.2",
|
|
||||||
"@lumeweb/kernel-dns-client": "^0.1.0-develop.7",
|
|
||||||
"@lumeweb/kernel-eth-client": "^0.1.0-develop.16",
|
|
||||||
"@lumeweb/kernel-handshake-client": "^0.1.0-develop.8",
|
|
||||||
"@lumeweb/kernel-ipfs-client": "^0.1.0-develop.24",
|
|
||||||
"@lumeweb/kernel-network-registry-client": "^0.1.0-develop.9",
|
|
||||||
"@lumeweb/kernel-peer-discovery-client": "^0.0.2-develop.16",
|
|
||||||
"@lumeweb/kernel-swarm-client": "^0.1.0-develop.10",
|
|
||||||
"@lumeweb/libkernel": "^0.1.0-develop.63",
|
|
||||||
"@lumeweb/tld-enum": "^0.1.0-develop.1",
|
|
||||||
"cheerio": "^1.0.0-rc.12",
|
|
||||||
"file-type": "^18.5.0",
|
|
||||||
"is-ipfs": "^8.0.1"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,25 +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";
|
|
||||||
import { createClient as createNetworkRegistryClient } from "@lumeweb/kernel-network-registry-client";
|
|
||||||
import { createClient as createHandshakeClient } from "@lumeweb/kernel-handshake-client";
|
|
||||||
import { createClient as createEthClient } from "@lumeweb/kernel-eth-client";
|
|
||||||
|
|
||||||
const dnsClient = createDnsClient();
|
|
||||||
const ipfsClient = createIpfsClient();
|
|
||||||
const swarmClient = createSwarmClient();
|
|
||||||
const peerDiscoveryClient = createPeerDiscoveryClient();
|
|
||||||
const networkRegistryClient = createNetworkRegistryClient();
|
|
||||||
const handshakeClient = createHandshakeClient();
|
|
||||||
const ethClient = createEthClient();
|
|
||||||
|
|
||||||
export {
|
|
||||||
dnsClient,
|
|
||||||
ipfsClient,
|
|
||||||
swarmClient,
|
|
||||||
peerDiscoveryClient,
|
|
||||||
networkRegistryClient,
|
|
||||||
handshakeClient,
|
|
||||||
ethClient,
|
|
||||||
};
|
|
|
@ -1,19 +0,0 @@
|
||||||
import { ContentFilter } from "./types.js";
|
|
||||||
|
|
||||||
export class ContentProcessor {
|
|
||||||
private filters: ContentFilter[] = [];
|
|
||||||
|
|
||||||
registerFilter(filter: ContentFilter) {
|
|
||||||
this.filters.push(filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
async process(response: Response, mimeType: string): Promise<Response> {
|
|
||||||
let processedResponse = response;
|
|
||||||
|
|
||||||
for (const filter of this.filters) {
|
|
||||||
processedResponse = await filter.process(processedResponse, mimeType);
|
|
||||||
}
|
|
||||||
|
|
||||||
return processedResponse;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,48 +0,0 @@
|
||||||
import { ContentFilter } from "src/types.js";
|
|
||||||
import { getTld } from "@lumeweb/libresolver";
|
|
||||||
import tldEnum from "@lumeweb/tld-enum";
|
|
||||||
import * as cheerio from "cheerio";
|
|
||||||
|
|
||||||
export default class URLRewriteFilter implements ContentFilter {
|
|
||||||
async process(response: Response, mimeType: string): Promise<Response> {
|
|
||||||
if (mimeType !== "text/html") {
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
let html = await response.text();
|
|
||||||
|
|
||||||
const $ = cheerio.load(html);
|
|
||||||
["a", "link", "script", "img"].forEach((tag) => {
|
|
||||||
$.root()
|
|
||||||
.find(tag)
|
|
||||||
.each((index, element) => {
|
|
||||||
let attrName = ["a", "link"].includes(tag) ? "href" : "src";
|
|
||||||
let urlValue = $(element).attr(attrName);
|
|
||||||
|
|
||||||
if (urlValue) {
|
|
||||||
if (!isICANN(urlValue)) {
|
|
||||||
if (urlValue.startsWith("/")) {
|
|
||||||
$(element).attr(attrName, `/browse${urlValue}`);
|
|
||||||
} else if (urlValue.startsWith("http")) {
|
|
||||||
$(element).attr(attrName, `/browse/${urlValue}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return new Response($.html(), {
|
|
||||||
headers: response.headers,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function isICANN(url: string) {
|
|
||||||
try {
|
|
||||||
const parsedUrl = new URL(url);
|
|
||||||
const domain = parsedUrl.hostname;
|
|
||||||
return tldEnum.list.includes(getTld(domain));
|
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
104
src/index.ts
104
src/index.ts
|
@ -1,104 +0,0 @@
|
||||||
import * as kernel from "@lumeweb/libkernel/kernel";
|
|
||||||
import {
|
|
||||||
dnsClient,
|
|
||||||
ethClient,
|
|
||||||
handshakeClient,
|
|
||||||
ipfsClient,
|
|
||||||
networkRegistryClient,
|
|
||||||
peerDiscoveryClient,
|
|
||||||
swarmClient,
|
|
||||||
} from "./clients.js";
|
|
||||||
import { ed25519 } from "@lumeweb/libkernel";
|
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", () =>
|
|
||||||
document.getElementById("go-button")?.addEventListener("click", () => {
|
|
||||||
let input = (
|
|
||||||
document.getElementById("address-bar") as HTMLInputElement
|
|
||||||
).value.trim();
|
|
||||||
|
|
||||||
// If the input doesn't contain a protocol, assume it's http
|
|
||||||
if (!input.match(/^https?:\/\//)) {
|
|
||||||
input = `http://${input}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Try to parse it as a URL
|
|
||||||
const url = new URL(input);
|
|
||||||
|
|
||||||
// Update the iframe's src attribute
|
|
||||||
const iframe = document.getElementById(
|
|
||||||
"web-content",
|
|
||||||
) as HTMLIFrameElement;
|
|
||||||
iframe.src = `/browse/${url.hostname}${url.pathname}${url.search}${url.hash}`;
|
|
||||||
} catch (e) {
|
|
||||||
// Handle invalid URLs here, if needed
|
|
||||||
console.error("Invalid URL:", e);
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
let BOOT_FUNCTIONS: (() => Promise<any>)[] = [];
|
|
||||||
|
|
||||||
async function boot() {
|
|
||||||
await kernel.init();
|
|
||||||
await kernel.login(ed25519.utils.randomPrivateKey());
|
|
||||||
|
|
||||||
const reg = await navigator.serviceWorker.register("/sw.js");
|
|
||||||
await reg.update();
|
|
||||||
|
|
||||||
await kernel.serviceWorkerReady();
|
|
||||||
|
|
||||||
BOOT_FUNCTIONS.push(
|
|
||||||
async () =>
|
|
||||||
await swarmClient.addRelay(
|
|
||||||
"2d7ae1517caf4aae4de73c6d6f400765d2dd00b69d65277a29151437ef1c7d1d",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// IRC
|
|
||||||
BOOT_FUNCTIONS.push(
|
|
||||||
async () =>
|
|
||||||
await peerDiscoveryClient.register(
|
|
||||||
"zdiN5eJ3RfHpZHTYorGxBt1GCsrGJYV9GprwVWkj8snGsjWSrptFm8BtQX",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
BOOT_FUNCTIONS.push(
|
|
||||||
async () => await networkRegistryClient.registerType("content"),
|
|
||||||
);
|
|
||||||
BOOT_FUNCTIONS.push(
|
|
||||||
async () => await networkRegistryClient.registerType("blockchain"),
|
|
||||||
);
|
|
||||||
BOOT_FUNCTIONS.push(async () => await handshakeClient.register());
|
|
||||||
BOOT_FUNCTIONS.push(async () => await ethClient.register());
|
|
||||||
BOOT_FUNCTIONS.push(async () => await ipfsClient.register());
|
|
||||||
|
|
||||||
const resolvers = [
|
|
||||||
"zdiJdDdBJWAdYFTcRa9So5TQQ9f1pYMiMy4dqYcKp9imomQtR11LJUyJyV", // CID
|
|
||||||
"zdiKvnZYNjDqXaM8uF3pGEs7Tt6jqGc7t7M4eqbvJwpkTnrZymncfUW9Cj", // ENS
|
|
||||||
"zrjEH3iojPLr7986o7iCn9THBmJmHiuDWmS1G6oT8DnfuFM", // HNS
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const resolver of resolvers) {
|
|
||||||
BOOT_FUNCTIONS.push(async () => dnsClient.registerResolver(resolver));
|
|
||||||
}
|
|
||||||
|
|
||||||
await bootup();
|
|
||||||
|
|
||||||
await Promise.all([
|
|
||||||
ethClient.ready(),
|
|
||||||
handshakeClient.ready(),
|
|
||||||
ipfsClient.ready(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
document.getElementById("booting")!.style.display = "none";
|
|
||||||
(document.getElementById("address-bar") as HTMLInputElement).disabled = false;
|
|
||||||
(document.getElementById("go-button") as HTMLButtonElement).disabled = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function bootup() {
|
|
||||||
for (const entry of Object.entries(BOOT_FUNCTIONS)) {
|
|
||||||
await entry[1]();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", boot);
|
|
123
src/messages.ts
123
src/messages.ts
|
@ -1,123 +0,0 @@
|
||||||
import exchangeCommunicationKeys from "./messages/exchangeCommunicationKeys.js";
|
|
||||||
import {
|
|
||||||
deleteQuery,
|
|
||||||
getAuthStatus,
|
|
||||||
getAuthStatusKnown,
|
|
||||||
getLoggedInDefer,
|
|
||||||
getQueries,
|
|
||||||
getQuery,
|
|
||||||
resetLoggedInDefer,
|
|
||||||
setAuthStatus,
|
|
||||||
setAuthStatusKnown,
|
|
||||||
getAuthStatusDefer,
|
|
||||||
} from "./vars.js";
|
|
||||||
const kernelMessageHandlers = {
|
|
||||||
exchangeCommunicationKeys,
|
|
||||||
};
|
|
||||||
|
|
||||||
export async function handleIncomingMessage(event: MessageEvent) {
|
|
||||||
if (event.source === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (event.source === window) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = event.data?.data;
|
|
||||||
if (event.data.method === "log") {
|
|
||||||
if (data?.isErr === false) {
|
|
||||||
console.log(data.message);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
console.error(data.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.data.method === "kernelAuthStatus") {
|
|
||||||
setAuthStatus(data);
|
|
||||||
if (!getAuthStatusKnown()) {
|
|
||||||
getAuthStatusDefer().resolve();
|
|
||||||
setAuthStatusKnown(true);
|
|
||||||
console.log("bootloader is now initialized");
|
|
||||||
if (!getAuthStatus().loginComplete) {
|
|
||||||
console.log("user is not logged in: waiting until login is confirmed");
|
|
||||||
} else {
|
|
||||||
getLoggedInDefer().resolve();
|
|
||||||
}
|
|
||||||
if (getAuthStatus().logoutComplete) {
|
|
||||||
resetLoggedInDefer();
|
|
||||||
setAuthStatusKnown(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!("nonce" in event.data)) {
|
|
||||||
(event.source as WindowProxy).postMessage(
|
|
||||||
{
|
|
||||||
nonce: "N/A",
|
|
||||||
method: "response",
|
|
||||||
err: "message sent to kernel with no nonce",
|
|
||||||
},
|
|
||||||
event.origin,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!("method" in event.data)) {
|
|
||||||
(event.source as WindowProxy).postMessage(
|
|
||||||
{
|
|
||||||
nonce: event.data.nonce,
|
|
||||||
method: "response",
|
|
||||||
err: "message sent to kernel with no method",
|
|
||||||
},
|
|
||||||
event.origin,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.data.method in kernelMessageHandlers) {
|
|
||||||
let response;
|
|
||||||
|
|
||||||
try {
|
|
||||||
response = await kernelMessageHandlers[event.data.method](
|
|
||||||
event.data.data,
|
|
||||||
);
|
|
||||||
} catch (e: any) {
|
|
||||||
response = { err: (e as Error).message };
|
|
||||||
}
|
|
||||||
|
|
||||||
(event.source as WindowProxy).postMessage(
|
|
||||||
{
|
|
||||||
nonce: event.data.nonce,
|
|
||||||
data: response,
|
|
||||||
},
|
|
||||||
event.origin,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!(event.data.nonce in getQueries())) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let receiveResult = getQuery(event.data.nonce);
|
|
||||||
if (event.data.method === "response") {
|
|
||||||
deleteQuery(event.data.nonce);
|
|
||||||
}
|
|
||||||
|
|
||||||
receiveResult?.(event.data);
|
|
||||||
|
|
||||||
if (["moduleCall", "response"].includes(event.data.method)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
(event.source as WindowProxy).postMessage(
|
|
||||||
{
|
|
||||||
nonce: event.data.nonce,
|
|
||||||
method: "response",
|
|
||||||
err:
|
|
||||||
"unrecognized method (user may need to log in): " + event.data.method,
|
|
||||||
},
|
|
||||||
event.origin,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
import { bytesToHex, hexToBytes } from "@lumeweb/libweb";
|
|
||||||
import {
|
|
||||||
getCommunicationPubKey,
|
|
||||||
setFrontendCommunicationPubkey,
|
|
||||||
} from "../vars.js";
|
|
||||||
|
|
||||||
export default function (data: any) {
|
|
||||||
setFrontendCommunicationPubkey(hexToBytes(data));
|
|
||||||
|
|
||||||
return bytesToHex(getCommunicationPubKey());
|
|
||||||
}
|
|
10
src/mimes.ts
10
src/mimes.ts
|
@ -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;
|
|
|
@ -1,39 +0,0 @@
|
||||||
import { ContentProcessor } from "./contentProcessor.js";
|
|
||||||
import { ContentProvider } from "./types.js";
|
|
||||||
import { DNSRecord, DNSResult } from "@lumeweb/libresolver";
|
|
||||||
import { URL } from "url";
|
|
||||||
|
|
||||||
export class ProviderManager {
|
|
||||||
private providers: ContentProvider[] = [];
|
|
||||||
|
|
||||||
private _processor = new ContentProcessor();
|
|
||||||
|
|
||||||
get processor(): ContentProcessor {
|
|
||||||
return this._processor;
|
|
||||||
}
|
|
||||||
|
|
||||||
register(provider: ContentProvider) {
|
|
||||||
this.providers.push(provider);
|
|
||||||
}
|
|
||||||
|
|
||||||
async fetch(dnsResult: DNSResult, path: string): Promise<Response> {
|
|
||||||
for (const record of dnsResult.records) {
|
|
||||||
for (const provider of this.providers) {
|
|
||||||
if (provider.supports(record.value)) {
|
|
||||||
const content = await provider.fetchContent(record.value, path);
|
|
||||||
|
|
||||||
if (content.headers.get("Content-Type")) {
|
|
||||||
return this._processor.process(
|
|
||||||
content,
|
|
||||||
content.headers.get("Content-Type")!,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error("No suitable provider found.");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,141 +0,0 @@
|
||||||
import { ContentProvider } from "src/types.js";
|
|
||||||
import { ipfsPath, ipnsPath, path as checkPath } from "is-ipfs";
|
|
||||||
import { createClient } from "@lumeweb/kernel-ipfs-client";
|
|
||||||
import { CID } from "multiformats/cid";
|
|
||||||
import type { UnixFSStats } from "@helia/unixfs";
|
|
||||||
import * as nodePath from "path";
|
|
||||||
import { fileTypeFromBuffer } from "file-type";
|
|
||||||
import extToMimes from "../mimes.js";
|
|
||||||
import { URL } from "url";
|
|
||||||
|
|
||||||
export default class IPFSProvider implements ContentProvider {
|
|
||||||
private _client = createClient();
|
|
||||||
async fetchContent(
|
|
||||||
uri: string,
|
|
||||||
path: string,
|
|
||||||
query?: string,
|
|
||||||
): Promise<Response> {
|
|
||||||
let cid = translatePath(uri);
|
|
||||||
let stat: UnixFSStats | null = null;
|
|
||||||
let urlPath = path;
|
|
||||||
const parsedPath = nodePath.parse(urlPath);
|
|
||||||
let err;
|
|
||||||
try {
|
|
||||||
if (ipnsPath(cid)) {
|
|
||||||
const cidHash = cid.replace("/ipns/", "");
|
|
||||||
cid = await this._client.ipns(cidHash);
|
|
||||||
cid = `/ipfs/${cid}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ipfsPath(cid)) {
|
|
||||||
cid = CID.parse(cid.replace("/ipfs/", "")).toV1().toString();
|
|
||||||
stat = await this._client.stat(cid);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
err = (e as Error).message;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = nodePath.join(urlPath, indexFile);
|
|
||||||
await this._client.stat(cid, {
|
|
||||||
path: subPath,
|
|
||||||
});
|
|
||||||
urlPath = subPath;
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!found) {
|
|
||||||
err = "404";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
await this._client.stat(cid, {
|
|
||||||
path: urlPath,
|
|
||||||
});
|
|
||||||
} catch {
|
|
||||||
err = "404";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (err) {
|
|
||||||
throw new Error(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let bufferRead = 0;
|
|
||||||
const fileTypeBufferLength = 4100;
|
|
||||||
const mimeBuffer: Uint8Array[] = [];
|
|
||||||
let reader = await this._client.cat(cid, { path: urlPath });
|
|
||||||
|
|
||||||
for await (const chunk of reader.iterable()) {
|
|
||||||
if (bufferRead < fileTypeBufferLength) {
|
|
||||||
if (chunk.length >= fileTypeBufferLength) {
|
|
||||||
mimeBuffer.push(chunk.slice(0, fileTypeBufferLength));
|
|
||||||
bufferRead += fileTypeBufferLength;
|
|
||||||
} else {
|
|
||||||
mimeBuffer.push(chunk);
|
|
||||||
bufferRead += chunk.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bufferRead >= fileTypeBufferLength) {
|
|
||||||
reader.abort();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
reader.abort();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mime;
|
|
||||||
|
|
||||||
if (bufferRead >= fileTypeBufferLength) {
|
|
||||||
const totalLength = mimeBuffer.reduce((acc, val) => acc + val.length, 0);
|
|
||||||
const concatenated = new Uint8Array(totalLength);
|
|
||||||
let offset = 0;
|
|
||||||
for (const chunk of mimeBuffer) {
|
|
||||||
concatenated.set(chunk, offset);
|
|
||||||
offset += chunk.length;
|
|
||||||
}
|
|
||||||
mime = await fileTypeFromBuffer(concatenated);
|
|
||||||
|
|
||||||
if (!mime) {
|
|
||||||
const ext = nodePath.parse(urlPath).ext.replace(".", "");
|
|
||||||
if (extToMimes.has(ext)) {
|
|
||||||
mime = extToMimes.get(ext);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
reader = await this._client.cat(cid, { path: urlPath });
|
|
||||||
|
|
||||||
const stream = new ReadableStream({
|
|
||||||
async start(controller) {
|
|
||||||
for await (const chunk of reader.iterable()) {
|
|
||||||
controller.enqueue(chunk);
|
|
||||||
}
|
|
||||||
controller.close();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return new Response(stream, {
|
|
||||||
headers: {
|
|
||||||
"Content-Type": mime ?? undefined,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
supports(uri: string): boolean {
|
|
||||||
return checkPath(translatePath(uri));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function translatePath(uri: string) {
|
|
||||||
return uri.replace(/:\/\//, "/").replace(/^/, "/");
|
|
||||||
}
|
|
69
src/sw.ts
69
src/sw.ts
|
@ -1,69 +0,0 @@
|
||||||
import { createClient as createDnsClient } from "@lumeweb/kernel-dns-client";
|
|
||||||
import { ProviderManager } from "./providerManager.js";
|
|
||||||
import IPFSProvider from "./providers/ipfs.js";
|
|
||||||
import URLRewriteFilter from "./filters/urlRewrite.js";
|
|
||||||
|
|
||||||
const dnsClient = createDnsClient();
|
|
||||||
|
|
||||||
const providerManager = new ProviderManager();
|
|
||||||
providerManager.register(new IPFSProvider());
|
|
||||||
providerManager.processor.registerFilter(new URLRewriteFilter());
|
|
||||||
|
|
||||||
globalThis.postMessage = async (...args) => {
|
|
||||||
// @ts-ignore
|
|
||||||
let ret = await clients.matchAll({ includeUncontrolled: true });
|
|
||||||
ret.forEach((item) => item.postMessage(...args));
|
|
||||||
|
|
||||||
if (!ret.length) {
|
|
||||||
const cb = (event) => {
|
|
||||||
// @ts-ignore
|
|
||||||
postMessage(...args);
|
|
||||||
self.removeEventListener("activate", cb);
|
|
||||||
};
|
|
||||||
self.addEventListener("activate", cb);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
self.addEventListener("activate", (event) => {
|
|
||||||
// @ts-ignore
|
|
||||||
event.waitUntil(
|
|
||||||
(async () => {
|
|
||||||
// @ts-ignore
|
|
||||||
await clients.claim();
|
|
||||||
// @ts-ignore
|
|
||||||
})(),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
addEventListener("fetch", (event: any) => {
|
|
||||||
event.respondWith(
|
|
||||||
(async () => {
|
|
||||||
const req = event.request;
|
|
||||||
const url = new URL(req.url);
|
|
||||||
|
|
||||||
if (
|
|
||||||
["/index.html", "/index.js", "/"].includes(url.pathname) ||
|
|
||||||
!url.pathname.startsWith("/browse/")
|
|
||||||
) {
|
|
||||||
return fetch(event.request).then((response: any) => {
|
|
||||||
response.redirectToFinalURL = true;
|
|
||||||
return response;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let realUrl = url.pathname.replace(/^\/browse\//, "").replace(/\/$/, "");
|
|
||||||
|
|
||||||
if (!realUrl.match(/^https?:\/\//)) {
|
|
||||||
realUrl = `http://${realUrl}`;
|
|
||||||
}
|
|
||||||
// Use your existing communication framework to resolve DNS.
|
|
||||||
const dnsResult = await dnsClient.resolve(new URL(realUrl).hostname);
|
|
||||||
|
|
||||||
if (!dnsResult.error && dnsResult.records.length > 0) {
|
|
||||||
return providerManager.fetch(dnsResult, new URL(realUrl).pathname);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Response("Sorry, that is not a valid web3 website.");
|
|
||||||
})(),
|
|
||||||
);
|
|
||||||
});
|
|
12
src/types.ts
12
src/types.ts
|
@ -1,12 +0,0 @@
|
||||||
export interface ContentProvider {
|
|
||||||
supports: (uri: string) => boolean;
|
|
||||||
fetchContent: (
|
|
||||||
uri: string,
|
|
||||||
path: string,
|
|
||||||
query?: string,
|
|
||||||
) => Promise<Response>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ContentFilter {
|
|
||||||
process: (response: Response, mineType: string) => Promise<Response>;
|
|
||||||
}
|
|
53
src/util.ts
53
src/util.ts
|
@ -1,53 +0,0 @@
|
||||||
import {
|
|
||||||
getKernelLoaded,
|
|
||||||
getLoginComplete,
|
|
||||||
getLogoutComplete,
|
|
||||||
} from "./vars.js";
|
|
||||||
import { objAsString } from "@lumeweb/libkernel";
|
|
||||||
|
|
||||||
export function sendAuthUpdate() {
|
|
||||||
window.parent.postMessage(
|
|
||||||
{
|
|
||||||
method: "kernelAuthStatus",
|
|
||||||
data: {
|
|
||||||
loginComplete: getLoginComplete(),
|
|
||||||
kernelLoaded: getKernelLoaded(),
|
|
||||||
logoutComplete: getLogoutComplete(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"*",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function bootloaderWLog(isErr: boolean, ...inputs: any) {
|
|
||||||
// Build the message, each item gets its own line. We do this because items
|
|
||||||
// are often full objects.
|
|
||||||
let message = "[lumeweb-kernel-bootloader]";
|
|
||||||
for (let i = 0; i < inputs.length; i++) {
|
|
||||||
message += "\n";
|
|
||||||
message += objAsString(inputs[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the log by sending it to the parent.
|
|
||||||
window.parent.postMessage(
|
|
||||||
{
|
|
||||||
method: "log",
|
|
||||||
data: {
|
|
||||||
isErr,
|
|
||||||
message,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"*",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function log(...inputs: any) {
|
|
||||||
bootloaderWLog(false, ...inputs);
|
|
||||||
}
|
|
||||||
export function logErr(...inputs: any) {
|
|
||||||
bootloaderWLog(true, ...inputs);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function reloadKernel() {
|
|
||||||
window.location.reload();
|
|
||||||
}
|
|
103
src/vars.ts
103
src/vars.ts
|
@ -1,103 +0,0 @@
|
||||||
import { x25519 } from "@noble/curves/ed25519";
|
|
||||||
import { defer } from "@lumeweb/libkernel/module";
|
|
||||||
import { KernelAuthStatus } from "@lumeweb/libkernel";
|
|
||||||
|
|
||||||
let queriesNonce = 1;
|
|
||||||
let queries: any = {};
|
|
||||||
let authStatus: KernelAuthStatus;
|
|
||||||
let authStatusKnown = false;
|
|
||||||
let authStatusDefer = defer();
|
|
||||||
let loggedInDefer = defer();
|
|
||||||
|
|
||||||
const store = new Map<string, any>(
|
|
||||||
Object.entries({
|
|
||||||
loginComplete: false,
|
|
||||||
logoutComplete: false,
|
|
||||||
kernelLoaded: "not yet",
|
|
||||||
communicationKey: null,
|
|
||||||
frontendCommunicationPubKey: null,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
export function setLoginComplete(status: boolean) {
|
|
||||||
store.set("loginComplete", status);
|
|
||||||
}
|
|
||||||
export function getLoginComplete(): boolean {
|
|
||||||
return store.get("loginComplete");
|
|
||||||
}
|
|
||||||
export function setLogoutComplete(status: boolean) {
|
|
||||||
store.set("logoutComplete", status);
|
|
||||||
}
|
|
||||||
export function getLogoutComplete(): boolean {
|
|
||||||
return store.get("logoutComplete");
|
|
||||||
}
|
|
||||||
export function setKernelLoaded(status: string) {
|
|
||||||
store.set("kernelLoaded", status);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getKernelLoaded(): string {
|
|
||||||
return store.get("kernelLoaded");
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getCommunicationKey(): Uint8Array {
|
|
||||||
if (!store.get("communicationKey")) {
|
|
||||||
store.set("communicationKey", x25519.utils.randomPrivateKey());
|
|
||||||
}
|
|
||||||
|
|
||||||
return store.get("communicationKey");
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getCommunicationPubKey() {
|
|
||||||
return x25519.getPublicKey(getCommunicationKey());
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getFrontendCommunicationPubkey(): Uint8Array {
|
|
||||||
return store.get("frontendCommunicationPubKey");
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setFrontendCommunicationPubkey(key: Uint8Array) {
|
|
||||||
store.set("frontendCommunicationPubKey", key);
|
|
||||||
}
|
|
||||||
export function getAuthStatusDefer() {
|
|
||||||
return authStatusDefer;
|
|
||||||
}
|
|
||||||
export function getQueriesNonce(): number {
|
|
||||||
return queriesNonce;
|
|
||||||
}
|
|
||||||
export function addQuery(nonce: any, func: Function) {
|
|
||||||
queries[nonce] = func;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function increaseQueriesNonce() {
|
|
||||||
queriesNonce++;
|
|
||||||
}
|
|
||||||
export function setAuthStatus(status: KernelAuthStatus) {
|
|
||||||
authStatus = status;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getAuthStatusKnown() {
|
|
||||||
return authStatusKnown;
|
|
||||||
}
|
|
||||||
export function setAuthStatusKnown(status: boolean) {
|
|
||||||
authStatusKnown = status;
|
|
||||||
}
|
|
||||||
export function getAuthStatus(): KernelAuthStatus {
|
|
||||||
return authStatus;
|
|
||||||
}
|
|
||||||
export function getLoggedInDefer() {
|
|
||||||
return loggedInDefer;
|
|
||||||
}
|
|
||||||
export function resetLoggedInDefer() {
|
|
||||||
loggedInDefer = defer();
|
|
||||||
}
|
|
||||||
export function getQueries() {
|
|
||||||
return queries;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getQuery(nonce: any) {
|
|
||||||
return queries[nonce];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deleteQuery(nonce: any) {
|
|
||||||
delete queries[nonce];
|
|
||||||
}
|
|
Loading…
Reference in New Issue