Compare commits
2 Commits
Author | SHA1 | Date |
---|---|---|
Derrick Hammer | 4228d834cd | |
Derrick Hammer | 00d1bd2ab5 |
|
@ -0,0 +1,68 @@
|
|||
<!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>
|
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"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"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
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,
|
||||
};
|
|
@ -0,0 +1,19 @@
|
|||
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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
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);
|
|
@ -0,0 +1,123 @@
|
|||
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;
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
import { bytesToHex, hexToBytes } from "@lumeweb/libweb";
|
||||
import {
|
||||
getCommunicationPubKey,
|
||||
setFrontendCommunicationPubkey,
|
||||
} from "../vars.js";
|
||||
|
||||
export default function (data: any) {
|
||||
setFrontendCommunicationPubkey(hexToBytes(data));
|
||||
|
||||
return bytesToHex(getCommunicationPubKey());
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
const extToMimes = new Map(
|
||||
Object.entries({
|
||||
html: "text/html",
|
||||
xhtml: "application/xhtml+xml",
|
||||
xml: "application/xml",
|
||||
})
|
||||
);
|
||||
Object.freeze(extToMimes);
|
||||
|
||||
export default extToMimes;
|
|
@ -0,0 +1,39 @@
|
|||
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.");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
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(/^/, "/");
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
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.");
|
||||
})(),
|
||||
);
|
||||
});
|
|
@ -0,0 +1,12 @@
|
|||
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>;
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
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();
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
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