*Skeleton starting point for new extension
This commit is contained in:
commit
250b09e5be
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"env": {
|
||||||
|
"browser": true,
|
||||||
|
"es2021": true,
|
||||||
|
"node": true
|
||||||
|
},
|
||||||
|
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": "latest",
|
||||||
|
"sourceType": "module"
|
||||||
|
},
|
||||||
|
"plugins": ["@typescript-eslint"],
|
||||||
|
"rules": {
|
||||||
|
"@typescript-eslint/no-explicit-any": "off",
|
||||||
|
"@typescript-eslint/no-non-null-assertion": "off",
|
||||||
|
"@typescript-eslint/no-var-requires": "off",
|
||||||
|
"prefer-const": "off",
|
||||||
|
"no-var": "off"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
package-lock.json
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022 Hammer Technologies LLC
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
|
@ -0,0 +1,5 @@
|
||||||
|
# Lume Web Extension
|
||||||
|
|
||||||
|
This is the WIP 0.3.0 extension for Lume Web. It uses the skynet kernel and uses most code from https://github.com/SkynetLabs/skynet-kernel/tree/beta/extension
|
||||||
|
|
||||||
|
If you have questions, from into the discord linked at [https://web3extension.com](https://web3extension.com)
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
After Width: | Height: | Size: 2.6 KiB |
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
|
@ -0,0 +1,30 @@
|
||||||
|
{
|
||||||
|
"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",
|
||||||
|
"homepage_url": "https://lumeweb.com",
|
||||||
|
"icons": {
|
||||||
|
"48": "icon.png",
|
||||||
|
"96": "icon@2x.png"
|
||||||
|
},
|
||||||
|
"permissions": ["proxy", "webRequest", "webRequestBlocking", "<all_urls>"],
|
||||||
|
"background": {
|
||||||
|
"scripts": ["background.js"]
|
||||||
|
},
|
||||||
|
"content_scripts": [
|
||||||
|
{
|
||||||
|
"matches": ["http://kernel.skynet/"],
|
||||||
|
"js": ["bootloader.js"],
|
||||||
|
"run_at": "document_end",
|
||||||
|
"all_frames": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matches": ["<all_urls>"],
|
||||||
|
"js": ["bridge.js"],
|
||||||
|
"run_at": "document_end",
|
||||||
|
"all_frames": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"web_accessible_resources": ["icon@2x.png", "auth.html"]
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
import esbuild from "esbuild";
|
||||||
|
|
||||||
|
esbuild.buildSync({
|
||||||
|
entryPoints: ["src/main/background.ts"],
|
||||||
|
outfile: "dist/background.js",
|
||||||
|
format: "iife",
|
||||||
|
bundle: true,
|
||||||
|
legalComments: "external",
|
||||||
|
// minify: true
|
||||||
|
define: {
|
||||||
|
global: "self",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
esbuild.buildSync({
|
||||||
|
entryPoints: ["src/main/bootloader.ts"],
|
||||||
|
outfile: "dist/bootloader.js",
|
||||||
|
format: "esm",
|
||||||
|
bundle: true,
|
||||||
|
legalComments: "external",
|
||||||
|
// minify: true
|
||||||
|
define: {
|
||||||
|
global: "self",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
esbuild.buildSync({
|
||||||
|
entryPoints: ["src/main/bridge.ts"],
|
||||||
|
outfile: "dist/bridge.js",
|
||||||
|
format: "esm",
|
||||||
|
bundle: true,
|
||||||
|
legalComments: "external",
|
||||||
|
// minify: true
|
||||||
|
define: {
|
||||||
|
global: "self",
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,36 @@
|
||||||
|
{
|
||||||
|
"name": "@lumeweb/extension",
|
||||||
|
"version": "0.3.0",
|
||||||
|
"description": "Lume Web is your decentralized gateway into the web3 internet, the web owned and controlled by its users",
|
||||||
|
"main": "index.js",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"deps": "npm install libskynet@latest",
|
||||||
|
"prettier": "prettier --write .",
|
||||||
|
"eslint": "eslint src --fix",
|
||||||
|
"lint": "npm run deps && npm run prettier && npm run eslint",
|
||||||
|
"compile": "node build.js",
|
||||||
|
"build": "npm run compile && cpy \"assets/*\" dist"
|
||||||
|
},
|
||||||
|
"author": "David Vorick",
|
||||||
|
"license": "MIT",
|
||||||
|
"devDependencies": {
|
||||||
|
"@lumeweb/webextension-polyfill": "https://github.com/LumeWeb/webextension-polyfill.git",
|
||||||
|
"@rollup/plugin-node-resolve": "^13.3.0",
|
||||||
|
"@types/read": "^0.0.29",
|
||||||
|
"@types/webextension-polyfill": "^0.9.0",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^5.18.0",
|
||||||
|
"cpy-cli": "^4.1.0",
|
||||||
|
"esbuild": "^0.14.51",
|
||||||
|
"eslint": "^8.13.0",
|
||||||
|
"prettier": "^2.6.2",
|
||||||
|
"rollup": "^2.75.6",
|
||||||
|
"typescript": "^4.6.3",
|
||||||
|
"webextension-polyfill": "^0.9.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@lumeweb/tld-enum": "https://github.com/LumeWeb/list-of-top-level-domains.git",
|
||||||
|
"libkernel": "^0.1.38",
|
||||||
|
"libskynet": "^0.0.62"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
import {
|
||||||
|
BlockingResponse,
|
||||||
|
OnBeforeRequestDetailsType,
|
||||||
|
OnHeadersReceivedDetailsType,
|
||||||
|
OnRequestDetailsType,
|
||||||
|
} from "../types";
|
||||||
|
import WebEngine from "../webEngine.js";
|
||||||
|
|
||||||
|
export default abstract class BaseProvider {
|
||||||
|
private engine: WebEngine;
|
||||||
|
constructor(engine: WebEngine) {
|
||||||
|
this.engine = engine;
|
||||||
|
}
|
||||||
|
|
||||||
|
async shouldHandleRequest(
|
||||||
|
details: OnBeforeRequestDetailsType
|
||||||
|
): Promise<boolean> {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleRequest(
|
||||||
|
details: OnBeforeRequestDetailsType
|
||||||
|
): Promise<BlockingResponse | boolean> {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleProxy(details: OnRequestDetailsType): Promise<any | boolean> {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleHeaders(
|
||||||
|
details: OnHeadersReceivedDetailsType
|
||||||
|
): Promise<BlockingResponse | boolean> {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,142 @@
|
||||||
|
import BaseProvider from "./baseProvider.js";
|
||||||
|
import {
|
||||||
|
BlockingResponse,
|
||||||
|
OnBeforeRequestDetailsType,
|
||||||
|
OnHeadersReceivedDetailsType,
|
||||||
|
OnRequestDetailsType,
|
||||||
|
} from "../types.js";
|
||||||
|
import browser from "@lumeweb/webextension-polyfill";
|
||||||
|
import { RequestOverrideResponse } from "libskynet";
|
||||||
|
import { queryKernel } from "../main/background.js";
|
||||||
|
import { requestProxies } from "../util.js";
|
||||||
|
|
||||||
|
export default class InternalProvider extends BaseProvider {
|
||||||
|
async shouldHandleRequest(
|
||||||
|
details: OnBeforeRequestDetailsType
|
||||||
|
): Promise<boolean> {
|
||||||
|
return [
|
||||||
|
"http://kernel.skynet/",
|
||||||
|
"http://kernel.skynet/auth.html",
|
||||||
|
"http://kernel.skynet/favicon.ico",
|
||||||
|
].includes(details.url);
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleRequest(
|
||||||
|
details: OnBeforeRequestDetailsType
|
||||||
|
): Promise<BlockingResponse | boolean> {
|
||||||
|
// For the kernel, we swallow the entire page. The 'bootloader' content
|
||||||
|
// script will everything that we need.
|
||||||
|
if (details.url === "http://kernel.skynet/") {
|
||||||
|
// Get the filter and swallow any response from the server.
|
||||||
|
let filter = browser.webRequest.filterResponseData(details.requestId);
|
||||||
|
filter.onstart = () => {
|
||||||
|
filter.close();
|
||||||
|
};
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// For the favicon, we make a request to a content script that has access
|
||||||
|
// to the favicon.
|
||||||
|
if (details.url === "http://kernel.skynet/favicon.ico") {
|
||||||
|
// Send a message to the kernel requesting an override for the
|
||||||
|
// favicon.ico. The kernel is itself loading this favicon from the
|
||||||
|
// browser, I just wasn't certain how to get binary objects directly to
|
||||||
|
// the background page, so we fetch it via a content script instead.
|
||||||
|
let faviconPromise = queryKernel({
|
||||||
|
method: "requestOverride",
|
||||||
|
data: {
|
||||||
|
url: details.url,
|
||||||
|
method: details.method,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get the filter and swallow any response from the server. Setting
|
||||||
|
// 'onData' to a blank function will swallow all data from the server.
|
||||||
|
let filter = browser.webRequest.filterResponseData(details.requestId);
|
||||||
|
filter.ondata = () => {
|
||||||
|
// By setting 'ondata' to the emtpy function, we effectively ensure
|
||||||
|
// that none of the data will be processed.
|
||||||
|
};
|
||||||
|
filter.onstop = () => {
|
||||||
|
faviconPromise.then((result: RequestOverrideResponse) => {
|
||||||
|
filter.write(result.body as Uint8Array);
|
||||||
|
filter.close();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// For the favicon, we make a request to a content script that has access
|
||||||
|
// to the favicon.
|
||||||
|
if (details.url === "http://kernel.skynet/auth.html") {
|
||||||
|
// Send a message to the kernel requesting an override for the auth
|
||||||
|
// page. The kernel is itself loading the auth page from the browser, I
|
||||||
|
// just wasn't certain how to get binary objects directly to the
|
||||||
|
// background page, so we fetch it via a content script instead.
|
||||||
|
let authPagePromise = queryKernel({
|
||||||
|
method: "requestOverride",
|
||||||
|
data: {
|
||||||
|
url: details.url,
|
||||||
|
method: details.method,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get the filter and swallow any response from the server. Setting
|
||||||
|
// 'onData' to a blank function will swallow all data from the server.
|
||||||
|
let filter = browser.webRequest.filterResponseData(details.requestId);
|
||||||
|
filter.ondata = () => {
|
||||||
|
// By setting 'ondata' to the emtpy function, we effectively ensure
|
||||||
|
// that none of the data will be processed.
|
||||||
|
};
|
||||||
|
filter.onstop = () => {
|
||||||
|
authPagePromise.then((result: RequestOverrideResponse) => {
|
||||||
|
filter.write(result.body as Uint8Array);
|
||||||
|
filter.close();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise do nothing.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleHeaders(
|
||||||
|
details: OnHeadersReceivedDetailsType
|
||||||
|
): Promise<OnRequestDetailsType | boolean> {
|
||||||
|
if (
|
||||||
|
details.url === "http://kernel.skynet/" ||
|
||||||
|
details.url === "http://kernel.skynet/auth.html"
|
||||||
|
) {
|
||||||
|
let headers = [
|
||||||
|
{
|
||||||
|
name: "content-type",
|
||||||
|
value: "text/html; charset=utf8",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return { responseHeaders: headers } as unknown as OnRequestDetailsType;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For the favicon, replace the headers with png headers.
|
||||||
|
if (details.url === "http://kernel.skynet/favicon.ico") {
|
||||||
|
let headers = [
|
||||||
|
{
|
||||||
|
name: "content-type",
|
||||||
|
value: "image/png",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return { responseHeaders: headers } as unknown as OnRequestDetailsType;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleProxy(details: OnRequestDetailsType): Promise<any> {
|
||||||
|
const hostname = new URL(details.url).hostname;
|
||||||
|
if (hostname === "kernel.skynet") {
|
||||||
|
return requestProxies;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,164 @@
|
||||||
|
import type { DataFn, KernelAuthStatus } from "libskynet";
|
||||||
|
|
||||||
|
declare var browser: any; // tsc
|
||||||
|
let queriesNonce = 1;
|
||||||
|
let queries: any = {};
|
||||||
|
let portsNonce = 0;
|
||||||
|
let openPorts = {} as any;
|
||||||
|
let timer = 20000;
|
||||||
|
|
||||||
|
function logLargeObjects() {
|
||||||
|
let queriesLen = Object.keys(queries).length;
|
||||||
|
let portsLen = Object.keys(openPorts).length;
|
||||||
|
if (queriesLen > 500) {
|
||||||
|
console.error("queries appears to be leaking:", queriesLen);
|
||||||
|
}
|
||||||
|
if (portsLen > 50) {
|
||||||
|
console.error("ports appears to be leaking:", portsLen);
|
||||||
|
}
|
||||||
|
timer *= 1.25;
|
||||||
|
setTimeout(logLargeObjects, timer);
|
||||||
|
}
|
||||||
|
setTimeout(logLargeObjects, timer);
|
||||||
|
|
||||||
|
let authStatus: KernelAuthStatus;
|
||||||
|
let authStatusKnown = false;
|
||||||
|
let authStatusResolve: DataFn;
|
||||||
|
let blockForBootloader = new Promise((resolve) => {
|
||||||
|
authStatusResolve = resolve;
|
||||||
|
});
|
||||||
|
|
||||||
|
export function queryKernel(query: any): Promise<any> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
let receiveResponse = function (data: any) {
|
||||||
|
resolve(data.data);
|
||||||
|
};
|
||||||
|
|
||||||
|
blockForBootloader.then(() => {
|
||||||
|
let nonce = queriesNonce;
|
||||||
|
queriesNonce += 1;
|
||||||
|
query.nonce = nonce;
|
||||||
|
queries[nonce] = receiveResponse;
|
||||||
|
if (kernelFrame.contentWindow !== null) {
|
||||||
|
kernelFrame.contentWindow.postMessage(query, "http://kernel.skynet");
|
||||||
|
} else {
|
||||||
|
console.error(
|
||||||
|
"kernelFrame.contentWindow was null, cannot send message!"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function handleKernelMessage(event: MessageEvent) {
|
||||||
|
if (event.origin !== "http://kernel.skynet") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let data = event.data.data;
|
||||||
|
|
||||||
|
if (event.data.method === "log") {
|
||||||
|
if (data.isErr === false) {
|
||||||
|
console.log(data.message);
|
||||||
|
} else {
|
||||||
|
console.error(data.message);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.data.method === "KernelAuthStatus") {
|
||||||
|
authStatus = data;
|
||||||
|
if (authStatusKnown === false) {
|
||||||
|
authStatusResolve();
|
||||||
|
authStatusKnown = true;
|
||||||
|
console.log("bootloader is now initialized");
|
||||||
|
if (authStatus.loginComplete !== true) {
|
||||||
|
console.log("user is not logged in: waiting until login is confirmed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let [, port] of Object.entries(openPorts)) {
|
||||||
|
try {
|
||||||
|
(port as any).postMessage(event.data);
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.logoutComplete === true) {
|
||||||
|
console.log("received logout signal, clearing all ports");
|
||||||
|
|
||||||
|
for (let [, port] of Object.entries(openPorts)) {
|
||||||
|
try {
|
||||||
|
(port as any).disconnect();
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
openPorts = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(event.data.nonce in queries)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let receiveResult = queries[event.data.nonce];
|
||||||
|
if (event.data.method === "response") {
|
||||||
|
delete queries[event.data.nonce];
|
||||||
|
}
|
||||||
|
|
||||||
|
receiveResult(event.data);
|
||||||
|
}
|
||||||
|
window.addEventListener("message", handleKernelMessage);
|
||||||
|
|
||||||
|
function handleBridgeMessage(
|
||||||
|
port: any,
|
||||||
|
portNonce: number,
|
||||||
|
data: any,
|
||||||
|
domain: string
|
||||||
|
) {
|
||||||
|
if (!("nonce" in data)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.method !== "queryUpdate") {
|
||||||
|
queries[data.nonce] = (response: any) => {
|
||||||
|
if (portNonce in openPorts) {
|
||||||
|
port.postMessage(response);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
data["domain"] = domain;
|
||||||
|
}
|
||||||
|
kernelFrame.contentWindow!.postMessage(data, "http://kernel.skynet");
|
||||||
|
}
|
||||||
|
function bridgeListener(port: any) {
|
||||||
|
let portNonce = portsNonce;
|
||||||
|
portsNonce++;
|
||||||
|
openPorts[portNonce] = port;
|
||||||
|
|
||||||
|
port.onDisconnect.addListener(() => {
|
||||||
|
delete openPorts[portNonce];
|
||||||
|
});
|
||||||
|
|
||||||
|
let domain = new URL(port.sender.url).hostname;
|
||||||
|
|
||||||
|
port.onMessage.addListener(function (data: any) {
|
||||||
|
handleBridgeMessage(port, portNonce, data, domain);
|
||||||
|
});
|
||||||
|
|
||||||
|
blockForBootloader.then(() => {
|
||||||
|
port.postMessage({
|
||||||
|
method: "KernelAuthStatus",
|
||||||
|
data: authStatus,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
browser.runtime.onConnect.addListener(bridgeListener);
|
||||||
|
|
||||||
|
import WebEngine from "../webEngine.js";
|
||||||
|
import InternalProvider from "../contentProviders/internalProvider.js";
|
||||||
|
|
||||||
|
const engine = new WebEngine();
|
||||||
|
engine.registerContentProvider(new InternalProvider(engine));
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
let kernelFrame: HTMLIFrameElement = document.createElement("iframe");
|
||||||
|
kernelFrame.src = "http://kernel.skynet";
|
||||||
|
document.body.appendChild(kernelFrame);
|
|
@ -0,0 +1,477 @@
|
||||||
|
import {
|
||||||
|
addContextToErr,
|
||||||
|
b64ToBuf,
|
||||||
|
bufToHex,
|
||||||
|
bufToStr,
|
||||||
|
computeRegistrySignature,
|
||||||
|
defaultPortalList,
|
||||||
|
deriveChildSeed,
|
||||||
|
deriveRegistryEntryID,
|
||||||
|
downloadSkylink,
|
||||||
|
Ed25519Keypair,
|
||||||
|
entryIDToSkylink,
|
||||||
|
Err,
|
||||||
|
hexToBuf,
|
||||||
|
progressiveFetch,
|
||||||
|
progressiveFetchResult,
|
||||||
|
taggedRegistryEntryKeys,
|
||||||
|
objAsString,
|
||||||
|
verifyRegistryWriteResponse,
|
||||||
|
} from "libskynet";
|
||||||
|
|
||||||
|
var browser: any; // tsc
|
||||||
|
|
||||||
|
const defaultKernelResolverLink =
|
||||||
|
"AQBFjXpEBwbMwkBwYg0gdkeAM-yy9vlajfLtZSee9f-MDg";
|
||||||
|
|
||||||
|
document.title = "kernel.skynet";
|
||||||
|
let header = document.createElement("h1");
|
||||||
|
header.textContent =
|
||||||
|
"Something went wrong! You should not be visiting this page, this page should only be accessed via an invisible iframe.";
|
||||||
|
document.body.appendChild(header);
|
||||||
|
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"*"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
function log(...inputs: any) {
|
||||||
|
bootloaderWLog(false, ...inputs);
|
||||||
|
}
|
||||||
|
function logErr(...inputs: any) {
|
||||||
|
bootloaderWLog(true, ...inputs);
|
||||||
|
}
|
||||||
|
|
||||||
|
var handleIncomingMessage = function (event: MessageEvent) {
|
||||||
|
if (event.source === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (event.source === window) {
|
||||||
|
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 === "requestOverride") {
|
||||||
|
handleSkynetKernelRequestOverride(event);
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
window.addEventListener("message", (event: MessageEvent) => {
|
||||||
|
handleIncomingMessage(event);
|
||||||
|
});
|
||||||
|
|
||||||
|
let kernelFavicon: Uint8Array;
|
||||||
|
let blockForFavicon: Promise<void> = new Promise((resolve) => {
|
||||||
|
try {
|
||||||
|
let faviconURL = browser.runtime.getURL("icon@2x.png");
|
||||||
|
fetch(faviconURL).then((response) => {
|
||||||
|
response.arrayBuffer().then((faviconData) => {
|
||||||
|
kernelFavicon = new Uint8Array(faviconData);
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
kernelFavicon = new Uint8Array(0);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let kernelAuthPage: Uint8Array;
|
||||||
|
let blockForAuthPage: Promise<void> = new Promise((resolve) => {
|
||||||
|
try {
|
||||||
|
let authURL = browser.runtime.getURL("auth.html");
|
||||||
|
fetch(authURL).then((response) => {
|
||||||
|
response.arrayBuffer().then((authData) => {
|
||||||
|
kernelAuthPage = new Uint8Array(authData);
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch (err: any) {
|
||||||
|
kernelAuthPage = new TextEncoder().encode(
|
||||||
|
addContextToErr(err, "unable to load the kernel auth page")
|
||||||
|
);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
function handleSkynetKernelRequestOverride(event: MessageEvent) {
|
||||||
|
if (event.source === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!event.origin.startsWith("moz")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (event.data.data.method !== "GET") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let respondOverride = function (headers: any, body: Uint8Array) {
|
||||||
|
(event.source as WindowProxy).postMessage(
|
||||||
|
{
|
||||||
|
nonce: event.data.nonce,
|
||||||
|
method: "response",
|
||||||
|
err: null,
|
||||||
|
data: {
|
||||||
|
override: true,
|
||||||
|
headers,
|
||||||
|
body,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
event.origin
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (event.data.data.url === "http://kernel.skynet/favicon.ico") {
|
||||||
|
blockForFavicon.then(() => {
|
||||||
|
let headers = [
|
||||||
|
{
|
||||||
|
name: "content-type",
|
||||||
|
value: "image/png",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
respondOverride(headers, kernelFavicon);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.data.data.url === "http://kernel.skynet/auth.html") {
|
||||||
|
blockForAuthPage.then(() => {
|
||||||
|
let headers = [
|
||||||
|
{
|
||||||
|
name: "content-type",
|
||||||
|
value: "text/html; charset=utf8",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
respondOverride(headers, kernelAuthPage);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
(event.source as WindowProxy).postMessage(
|
||||||
|
{
|
||||||
|
nonce: event.data.nonce,
|
||||||
|
method: "response",
|
||||||
|
err: null,
|
||||||
|
data: {
|
||||||
|
override: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
event.origin
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
var handleStorage = function (event: StorageEvent) {
|
||||||
|
if (event.key !== null && event.key !== "v1-seed") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (logoutComplete === true) {
|
||||||
|
window.location.reload();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.key === null && loginComplete === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.key === "v1-seed" && loginComplete === false) {
|
||||||
|
let userSeedString = window.localStorage.getItem("v1-seed");
|
||||||
|
if (userSeedString === null) {
|
||||||
|
sendAuthUpdate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let [decodedSeed, errHTB] = hexToBuf(userSeedString);
|
||||||
|
if (errHTB !== null) {
|
||||||
|
logErr(addContextToErr(errHTB, "seed could not be decoded from hex"));
|
||||||
|
sendAuthUpdate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
userSeed = decodedSeed;
|
||||||
|
|
||||||
|
log("user is now logged in, attempting to load kernel");
|
||||||
|
loginComplete = true;
|
||||||
|
loadKernel();
|
||||||
|
sendAuthUpdate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logoutComplete = true;
|
||||||
|
sendAuthUpdate();
|
||||||
|
log("attempting to do a page reload");
|
||||||
|
window.location.reload();
|
||||||
|
};
|
||||||
|
window.addEventListener("storage", (event) => handleStorage(event));
|
||||||
|
|
||||||
|
function downloadKernel(
|
||||||
|
kernelSkylink: string
|
||||||
|
): Promise<[kernelCode: string, err: Err]> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
downloadSkylink(kernelSkylink).then(([fileData, err]) => {
|
||||||
|
if (err === "404") {
|
||||||
|
resolve(["", err]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err !== null) {
|
||||||
|
resolve([
|
||||||
|
"",
|
||||||
|
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(defaultKernelResolverLink);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
logErr(extErr);
|
||||||
|
sendAuthUpdate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
window.eval(kernelCode);
|
||||||
|
kernelLoaded = "success";
|
||||||
|
sendAuthUpdate();
|
||||||
|
log("kernel successfully loaded");
|
||||||
|
return;
|
||||||
|
} catch (err: any) {
|
||||||
|
let extErr = addContextToErr(err, "unable to eval kernel");
|
||||||
|
kernelLoaded = extErr;
|
||||||
|
logErr(extErr);
|
||||||
|
logErr(err.toString());
|
||||||
|
console.error(extErr);
|
||||||
|
console.error(err);
|
||||||
|
sendAuthUpdate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let loginComplete = false;
|
||||||
|
let logoutComplete = false;
|
||||||
|
let kernelLoaded = "not yet";
|
||||||
|
function sendAuthUpdate() {
|
||||||
|
window.parent.postMessage(
|
||||||
|
{
|
||||||
|
method: "kernelAuthStatus",
|
||||||
|
data: {
|
||||||
|
loginComplete: loginComplete,
|
||||||
|
kernelLoaded: kernelLoaded,
|
||||||
|
logoutComplete: logoutComplete,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"*"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
sendAuthUpdate();
|
||||||
|
|
||||||
|
let userSeed: Uint8Array;
|
||||||
|
function checkForLoadKernel() {
|
||||||
|
let userSeedString = window.localStorage.getItem("v1-seed");
|
||||||
|
if (userSeedString === null) {
|
||||||
|
sendAuthUpdate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let [decodedSeed, errHTB] = hexToBuf(userSeedString);
|
||||||
|
if (errHTB !== null) {
|
||||||
|
logErr(addContextToErr(errHTB, "seed could not be decoded from hex"));
|
||||||
|
sendAuthUpdate();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
userSeed = decodedSeed;
|
||||||
|
|
||||||
|
log("user is already logged in, attempting to load kernel");
|
||||||
|
loginComplete = true;
|
||||||
|
sendAuthUpdate();
|
||||||
|
loadKernel();
|
||||||
|
}
|
||||||
|
|
||||||
|
let accessFailedStr =
|
||||||
|
"unable to get access to localStorage, user may need to reduce their privacy settings";
|
||||||
|
if (
|
||||||
|
Object.prototype.hasOwnProperty.call(document, "requestStorageAccess") &&
|
||||||
|
window.origin === "https://skt.us"
|
||||||
|
) {
|
||||||
|
document
|
||||||
|
.requestStorageAccess()
|
||||||
|
.then(() => {
|
||||||
|
checkForLoadKernel();
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
log(addContextToErr(err, accessFailedStr));
|
||||||
|
sendAuthUpdate();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
checkForLoadKernel();
|
||||||
|
}
|
|
@ -0,0 +1,129 @@
|
||||||
|
export {};
|
||||||
|
|
||||||
|
// Need to declare the browser variable so typescript doesn't complain. We
|
||||||
|
// declare it as 'any' because typescript doesn't know type 'Browser'.
|
||||||
|
declare var browser: any;
|
||||||
|
|
||||||
|
// This is the same as the dataFn declared in libskynet, but since it is the
|
||||||
|
// only import I decided to re-implement it here.
|
||||||
|
type dataFn = (data?: any) => void;
|
||||||
|
|
||||||
|
// Create the object that will track the current auth status of the kernel. We
|
||||||
|
// need a promise because the client may connect to the bridge after all of the
|
||||||
|
// auth messages have been processed, there's no guarantee that the client will
|
||||||
|
// catch the initial messages.
|
||||||
|
//
|
||||||
|
// blockForAuthStatus is a promise that will resolve when the auth status is
|
||||||
|
// initially known. 'authStatus' is the object that contains the latest auth
|
||||||
|
// information from
|
||||||
|
let authStatus: any; // matches the data field of the kernelAuthStatus message
|
||||||
|
let authStatusKnown = false;
|
||||||
|
let authStatusResolve: dataFn;
|
||||||
|
let blockForAuthStatus: Promise<void> = new Promise((resolve) => {
|
||||||
|
authStatusResolve = resolve;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create the handler for messages from the background page. The background
|
||||||
|
// will be exclusively relaying messages from the bridge to the kernel.
|
||||||
|
function handleBackgroundMessage(data: any) {
|
||||||
|
// If this is the first auth status message from the kernel, resolve the
|
||||||
|
// auth promise.
|
||||||
|
if (data.method === "kernelAuthStatus") {
|
||||||
|
authStatus = data.data;
|
||||||
|
if (authStatusKnown === false) {
|
||||||
|
authStatusKnown = true;
|
||||||
|
authStatusResolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pass the message through to the main page.
|
||||||
|
window.postMessage(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect to the background page.
|
||||||
|
let port = browser.runtime.connect();
|
||||||
|
port.onMessage.addListener(handleBackgroundMessage);
|
||||||
|
|
||||||
|
// handleVersion will send a response providing the version of the bridge. When
|
||||||
|
// the bridge version is queried, the bridge assumes that a new consumer has
|
||||||
|
// appeared which will need the auth status of the kernel repeated.
|
||||||
|
function handleVersion(data: any) {
|
||||||
|
// Send a message indicating that the bridge is alive.
|
||||||
|
window.postMessage({
|
||||||
|
nonce: data.nonce,
|
||||||
|
method: "response",
|
||||||
|
err: null,
|
||||||
|
data: {
|
||||||
|
version: "v0.2.0",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Wait until the kernel auth status is known, then send a message with
|
||||||
|
// the kernel auth status.
|
||||||
|
blockForAuthStatus.then(() => {
|
||||||
|
window.postMessage({
|
||||||
|
method: "kernelAuthStatus",
|
||||||
|
data: authStatus,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleKernelQuery handles messages sent by the page that are intended to
|
||||||
|
// eventually reach the kernel.
|
||||||
|
function handleKernelQuery(data: any) {
|
||||||
|
// Check for a kernel query. We already checked that a nonce exists.
|
||||||
|
if (!("data" in data)) {
|
||||||
|
window.postMessage({
|
||||||
|
nonce: data.nonce,
|
||||||
|
method: "response",
|
||||||
|
err: "missing data from newKernelQuery message: " + JSON.stringify(data),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pass the message along to the kernel. The caller is responsible for
|
||||||
|
// ensuring the nonce is unique.
|
||||||
|
port.postMessage(data.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleQueryUpdate will forward an update to a query to the kernel.
|
||||||
|
function handleQueryUpdate(data: any) {
|
||||||
|
// Send the update to the kernel.
|
||||||
|
port.postMessage(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is the listener for the content script, it will receive messages from
|
||||||
|
// the page script that it can forward to the kernel.
|
||||||
|
function handleMessage(event: MessageEvent) {
|
||||||
|
// Throughout this function, errors are typically not logged because we
|
||||||
|
// will be receiving all messages sent to the window, including messages
|
||||||
|
// that have nothing to do with the Skynet kernel protocol. If there is an
|
||||||
|
// error, we generally assume that the message has an unrelated purpose and
|
||||||
|
// doesn't need to be logged.
|
||||||
|
|
||||||
|
// Authenticate the message as a message from the kernel.
|
||||||
|
if (event.source !== window) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Check that a nonce and method were both provided.
|
||||||
|
if (!("nonce" in event.data) || !("method" in event.data)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Switch on the method.
|
||||||
|
if (event.data.method === "kernelBridgeVersion") {
|
||||||
|
handleVersion(event.data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (event.data.method === "newKernelQuery") {
|
||||||
|
handleKernelQuery(event.data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (event.data.method === "queryUpdate") {
|
||||||
|
handleQueryUpdate(event.data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Everything else just gets ignored.
|
||||||
|
}
|
||||||
|
window.addEventListener("message", handleMessage);
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { WebRequest, Proxy } from "webextension-polyfill";
|
||||||
|
import OnHeadersReceivedDetailsType = WebRequest.OnHeadersReceivedDetailsType;
|
||||||
|
import OnBeforeRequestDetailsType = WebRequest.OnBeforeRequestDetailsType;
|
||||||
|
import OnCompletedDetailsType = WebRequest.OnCompletedDetailsType;
|
||||||
|
import OnErrorOccurredDetailsType = WebRequest.OnErrorOccurredDetailsType;
|
||||||
|
import BlockingResponse = WebRequest.BlockingResponse;
|
||||||
|
import OnRequestDetailsType = Proxy.OnRequestDetailsType;
|
||||||
|
|
||||||
|
export {
|
||||||
|
OnHeadersReceivedDetailsType,
|
||||||
|
OnBeforeRequestDetailsType,
|
||||||
|
OnCompletedDetailsType,
|
||||||
|
OnErrorOccurredDetailsType,
|
||||||
|
BlockingResponse,
|
||||||
|
OnRequestDetailsType,
|
||||||
|
};
|
|
@ -0,0 +1,23 @@
|
||||||
|
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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isDomain(domain: string) {
|
||||||
|
return /(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]/.test(
|
||||||
|
domain
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeDomain(domain: string): string {
|
||||||
|
return domain.replace(/^\.+|\.+$/g, "").replace(/^\/+|\/+$/g, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
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 },
|
||||||
|
];
|
|
@ -0,0 +1,128 @@
|
||||||
|
import browser from "@lumeweb/webextension-polyfill";
|
||||||
|
|
||||||
|
import BaseProvider from "./contentProviders/baseProvider.js";
|
||||||
|
import {
|
||||||
|
BlockingResponse,
|
||||||
|
OnBeforeRequestDetailsType,
|
||||||
|
OnCompletedDetailsType,
|
||||||
|
OnErrorOccurredDetailsType,
|
||||||
|
OnHeadersReceivedDetailsType,
|
||||||
|
OnRequestDetailsType,
|
||||||
|
} from "./types";
|
||||||
|
|
||||||
|
export default class WebEngine {
|
||||||
|
private contentProviders: BaseProvider[] = [];
|
||||||
|
private requests: Map<string, OnBeforeRequestDetailsType> = new Map();
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
browser.webRequest.onHeadersReceived.addListener(
|
||||||
|
this.headerHandler.bind(this),
|
||||||
|
{ urls: ["<all_urls>"] },
|
||||||
|
["blocking", "responseHeaders"]
|
||||||
|
);
|
||||||
|
browser.proxy.onRequest.addListener(this.proxyHandler.bind(this), {
|
||||||
|
urls: ["<all_urls>"],
|
||||||
|
});
|
||||||
|
browser.webRequest.onBeforeRequest.addListener(
|
||||||
|
this.requestHandler.bind(this),
|
||||||
|
{ urls: ["<all_urls>"] },
|
||||||
|
["blocking"]
|
||||||
|
);
|
||||||
|
browser.webRequest.onCompleted.addListener(
|
||||||
|
this.onCompletedHandler.bind(this),
|
||||||
|
{
|
||||||
|
urls: ["<all_urls>"],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
browser.webRequest.onErrorOccurred.addListener(
|
||||||
|
this.onErrorHandler.bind(this),
|
||||||
|
{
|
||||||
|
urls: ["<all_urls>"],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async headerHandler(
|
||||||
|
details: OnHeadersReceivedDetailsType
|
||||||
|
): Promise<BlockingResponse> {
|
||||||
|
const def = { responseHeaders: details.responseHeaders };
|
||||||
|
if (!this.requests.has(details.requestId)) {
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const provider of this.contentProviders) {
|
||||||
|
const response = await provider.handleHeaders(details);
|
||||||
|
if (response !== false) {
|
||||||
|
return response as BlockingResponse;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async proxyHandler(details: OnRequestDetailsType): Promise<any> {
|
||||||
|
let handle = false;
|
||||||
|
for (const provider of this.contentProviders) {
|
||||||
|
if (await provider.shouldHandleRequest(details)) {
|
||||||
|
handle = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!handle) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
this.requests.set(details.requestId, details);
|
||||||
|
|
||||||
|
for (const provider of this.contentProviders) {
|
||||||
|
const response = await provider.handleProxy(details);
|
||||||
|
if (response !== false) {
|
||||||
|
return response as any;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async requestHandler(
|
||||||
|
details: OnBeforeRequestDetailsType
|
||||||
|
): Promise<BlockingResponse> {
|
||||||
|
if (!this.requests.has(details.requestId)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const provider of this.contentProviders) {
|
||||||
|
const response = await provider.handleRequest(details);
|
||||||
|
if (response !== false) {
|
||||||
|
return response as BlockingResponse;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async onCompletedHandler(
|
||||||
|
details: OnCompletedDetailsType
|
||||||
|
): Promise<void> {
|
||||||
|
if (this.requests.has(details.requestId)) {
|
||||||
|
this.requests.delete(details.requestId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async onErrorHandler(
|
||||||
|
details: OnErrorOccurredDetailsType
|
||||||
|
): Promise<void> {
|
||||||
|
if (this.requests.has(details.requestId)) {
|
||||||
|
this.requests.delete(details.requestId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public registerContentProvider(provider: BaseProvider) {
|
||||||
|
if (this.contentProviders.includes(provider)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.contentProviders.push(provider);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "esnext",
|
||||||
|
"target": "esnext",
|
||||||
|
"declaration": true,
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"outDir": "./build",
|
||||||
|
"strict": true,
|
||||||
|
"allowSyntheticDefaultImports": true
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
"exclude": ["node_modules", "**/__tests__/*"]
|
||||||
|
}
|
Reference in New Issue