refactor: split handleIncomingMessage to its own file, set activePortalMasterKey, call maybeInitDefaultPortals
This commit is contained in:
parent
fcca0e2cc2
commit
63fc62401b
161
src/index.ts
161
src/index.ts
|
@ -1,152 +1,39 @@
|
||||||
// This is the business logic for the Skynet kernel, responsible for
|
|
||||||
// downloading and running modules, managing queries between modules and
|
|
||||||
// applications, managing user overrides, and other core functionalities.
|
|
||||||
|
|
||||||
// NOTE: Anything and anyone can send messages to the kernel. All data that
|
|
||||||
// gets received is untrusted and potentially maliciously crafted. Type
|
|
||||||
// checking is very important.
|
|
||||||
|
|
||||||
import { notableErrors, respondErr } from "./err.js";
|
|
||||||
import { logLargeObjects } from "./logLargeState.js";
|
import { logLargeObjects } from "./logLargeState.js";
|
||||||
import { log, logErr } from "./log.js";
|
import { log, logErr } from "./log.js";
|
||||||
import { handleModuleCall, handleQueryUpdate } from "./queries.js";
|
|
||||||
import { KERNEL_DISTRO, KERNEL_VERSION } from "./version.js";
|
import { KERNEL_DISTRO, KERNEL_VERSION } from "./version.js";
|
||||||
import { setActivePortals } from "@lumeweb/libweb";
|
import {
|
||||||
|
maybeInitDefaultPortals,
|
||||||
|
setActivePortalMasterKey,
|
||||||
|
} from "@lumeweb/libweb";
|
||||||
import { Client } from "@lumeweb/libportal";
|
import { Client } from "@lumeweb/libportal";
|
||||||
|
import { addContextToErr } from "@lumeweb/libkernel";
|
||||||
|
import { handleIncomingMessage } from "./message.js";
|
||||||
|
import { activeKey } from "./key.js";
|
||||||
|
|
||||||
// These three functions are expected to have already been declared by the
|
declare global {
|
||||||
// bootloader. They are necessary for getting started and downloading the
|
interface Window {
|
||||||
// kernel while informing applications about the auth state of the kernel.
|
bootloaderPortals: Client[];
|
||||||
//
|
}
|
||||||
// The kernel is encouraged to overwrite these functions with new values.
|
}
|
||||||
declare let handleIncomingMessage: (event: MessageEvent) => void;
|
|
||||||
declare let handleSkynetKernelRequestOverride: (event: MessageEvent) => void;
|
|
||||||
declare let bootloaderPortals: Client[];
|
|
||||||
|
|
||||||
// IS_EXTENSION is a boolean that indicates whether or not the kernel is
|
|
||||||
// running in a browser extension.
|
|
||||||
const IS_EXTENSION = window.origin === "http://kernel.lume";
|
|
||||||
|
|
||||||
// Kick off the thread that will periodically log all of the large objects in
|
// Kick off the thread that will periodically log all of the large objects in
|
||||||
// the kernel, so that it's easier to check for memory leaks.
|
// the kernel, so that it's easier to check for memory leaks.
|
||||||
logLargeObjects();
|
logLargeObjects();
|
||||||
|
|
||||||
// Establish the stateful variable for tracking module overrides.
|
|
||||||
let moduleOverrideList = {} as any;
|
|
||||||
|
|
||||||
// Set up portals based on the instances created in the bootloader
|
|
||||||
setActivePortals(bootloaderPortals);
|
|
||||||
|
|
||||||
// Write a log that declares the kernel version and distribution.
|
// Write a log that declares the kernel version and distribution.
|
||||||
log("init", "Lume Web Kernel v" + KERNEL_VERSION + "-" + KERNEL_DISTRO);
|
log("init", "Lume Web Kernel v" + KERNEL_VERSION + "-" + KERNEL_DISTRO);
|
||||||
|
|
||||||
// Overwrite the handleIncomingMessage function that gets called at the end of the
|
/*
|
||||||
// event handler, allowing us to support custom messages.
|
Try to load either our saved portal(s) or the default portal(s)
|
||||||
handleIncomingMessage = function (event: any) {
|
*/
|
||||||
// Ignore all messages from ourself.
|
setActivePortalMasterKey(activeKey);
|
||||||
if (event.source === window) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Input validation.
|
let [, portalLoadErr] = maybeInitDefaultPortals();
|
||||||
if (!("method" in event.data)) {
|
if (portalLoadErr) {
|
||||||
logErr("handleIncomingMessage", "kernel request is missing 'method' field");
|
let err = addContextToErr(portalLoadErr, "unable to init portals");
|
||||||
return;
|
logErr(err);
|
||||||
}
|
}
|
||||||
if (!("nonce" in event.data)) {
|
|
||||||
logErr(
|
|
||||||
"handleIncomingMessage",
|
|
||||||
"message sent to kernel with no nonce field",
|
|
||||||
event.data,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Establish a debugging handler that a developer can call to verify
|
if (!portalLoadErr) {
|
||||||
// that round-trip communication has been correctly programmed between
|
window.addEventListener("message", handleIncomingMessage);
|
||||||
// the kernel and the calling application.
|
}
|
||||||
//
|
|
||||||
// It was easier to inline the message than to abstract it.
|
|
||||||
if (event.data.method === "version") {
|
|
||||||
event.source.postMessage(
|
|
||||||
{
|
|
||||||
nonce: event.data.nonce,
|
|
||||||
method: "response",
|
|
||||||
err: null,
|
|
||||||
data: {
|
|
||||||
distribution: KERNEL_DISTRO,
|
|
||||||
version: KERNEL_VERSION,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
event.origin,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Establish a debugging handler to return any noteworthy errors that the
|
|
||||||
// kernel has encountered. This is mainly intended to be used by the test
|
|
||||||
// suite.
|
|
||||||
if (event.data.method === "checkErrs") {
|
|
||||||
event.source.postMessage(
|
|
||||||
{
|
|
||||||
nonce: event.data.nonce,
|
|
||||||
method: "response",
|
|
||||||
err: null,
|
|
||||||
data: {
|
|
||||||
errs: notableErrors,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
event.origin,
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Establish handlers for the major kernel methods.
|
|
||||||
if (event.data.method === "moduleCall") {
|
|
||||||
// Check for a domain. If the message was sent by a browser
|
|
||||||
// extension, we trust the domain provided by the extension,
|
|
||||||
// otherwise we use the domain of the parent as the domain.
|
|
||||||
// This does mean that the kernel is trusting that the user has
|
|
||||||
// no malicious browser extensions, as we aren't checking for
|
|
||||||
// **which** extension is sending the message, we are only
|
|
||||||
// checking that the message is coming from a browser
|
|
||||||
// extension.
|
|
||||||
if (event.origin.startsWith("moz") && !("domain" in event.data)) {
|
|
||||||
logErr(
|
|
||||||
"moduleCall",
|
|
||||||
"caller is an extension, but no domain was provided",
|
|
||||||
);
|
|
||||||
respondErr(
|
|
||||||
event,
|
|
||||||
event.source,
|
|
||||||
false,
|
|
||||||
"caller is an extension, but not domain was provided",
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let domain;
|
|
||||||
if (event.origin.startsWith("moz")) {
|
|
||||||
domain = event.data.domain;
|
|
||||||
} else {
|
|
||||||
domain = new URL(event.origin).hostname;
|
|
||||||
}
|
|
||||||
handleModuleCall(event, event.source, domain, false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (event.data.method === "queryUpdate") {
|
|
||||||
handleQueryUpdate(event);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (event.data.method === "requestOverride") {
|
|
||||||
handleSkynetKernelRequestOverride(event);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unrecognized method, reject the query.
|
|
||||||
respondErr(
|
|
||||||
event,
|
|
||||||
event.source,
|
|
||||||
false,
|
|
||||||
"unrecognized method: " + event.data.method,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
// This variable is the key that got loaded into memory by the bootloader, and
|
import { hexToBytes } from "@lumeweb/libweb";
|
||||||
// is the user key. We keep this key in memory, because if the user ever logs
|
|
||||||
// out the kernel is expected to refresh, which will clear the key.
|
|
||||||
declare let userKey: Uint8Array;
|
|
||||||
|
|
||||||
export const activeKey = userKey;
|
export const activeKey = hexToBytes(
|
||||||
|
window.localStorage.getItem("key") as string,
|
||||||
|
);
|
||||||
|
|
|
@ -0,0 +1,111 @@
|
||||||
|
// Overwrite the handleIncomingMessage function that gets called at the end of the
|
||||||
|
// event handler, allowing us to support custom messages.
|
||||||
|
import { logErr } from "./log.js";
|
||||||
|
import { KERNEL_DISTRO, KERNEL_VERSION } from "./version.js";
|
||||||
|
import { notableErrors, respondErr } from "./err.js";
|
||||||
|
import { handleModuleCall, handleQueryUpdate } from "./queries.js";
|
||||||
|
|
||||||
|
export const handleIncomingMessage = function (event: any) {
|
||||||
|
// Ignore all messages from ourself.
|
||||||
|
if (event.source === window) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Input validation.
|
||||||
|
if (!("method" in event.data)) {
|
||||||
|
logErr("handleIncomingMessage", "kernel request is missing 'method' field");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!("nonce" in event.data)) {
|
||||||
|
logErr(
|
||||||
|
"handleIncomingMessage",
|
||||||
|
"message sent to kernel with no nonce field",
|
||||||
|
event.data,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Establish a debugging handler that a developer can call to verify
|
||||||
|
// that round-trip communication has been correctly programmed between
|
||||||
|
// the kernel and the calling application.
|
||||||
|
//
|
||||||
|
// It was easier to inline the message than to abstract it.
|
||||||
|
if (event.data.method === "version") {
|
||||||
|
event.source.postMessage(
|
||||||
|
{
|
||||||
|
nonce: event.data.nonce,
|
||||||
|
method: "response",
|
||||||
|
err: null,
|
||||||
|
data: {
|
||||||
|
distribution: KERNEL_DISTRO,
|
||||||
|
version: KERNEL_VERSION,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
event.origin,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Establish a debugging handler to return any noteworthy errors that the
|
||||||
|
// kernel has encountered. This is mainly intended to be used by the test
|
||||||
|
// suite.
|
||||||
|
if (event.data.method === "checkErrs") {
|
||||||
|
event.source.postMessage(
|
||||||
|
{
|
||||||
|
nonce: event.data.nonce,
|
||||||
|
method: "response",
|
||||||
|
err: null,
|
||||||
|
data: {
|
||||||
|
errs: notableErrors,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
event.origin,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Establish handlers for the major kernel methods.
|
||||||
|
if (event.data.method === "moduleCall") {
|
||||||
|
// Check for a domain. If the message was sent by a browser
|
||||||
|
// extension, we trust the domain provided by the extension,
|
||||||
|
// otherwise we use the domain of the parent as the domain.
|
||||||
|
// This does mean that the kernel is trusting that the user has
|
||||||
|
// no malicious browser extensions, as we aren't checking for
|
||||||
|
// **which** extension is sending the message, we are only
|
||||||
|
// checking that the message is coming from a browser
|
||||||
|
// extension.
|
||||||
|
if (event.origin.startsWith("moz") && !("domain" in event.data)) {
|
||||||
|
logErr(
|
||||||
|
"moduleCall",
|
||||||
|
"caller is an extension, but no domain was provided",
|
||||||
|
);
|
||||||
|
respondErr(
|
||||||
|
event,
|
||||||
|
event.source,
|
||||||
|
false,
|
||||||
|
"caller is an extension, but not domain was provided",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let domain;
|
||||||
|
if (event.origin.startsWith("moz")) {
|
||||||
|
domain = event.data.domain;
|
||||||
|
} else {
|
||||||
|
domain = new URL(event.origin).hostname;
|
||||||
|
}
|
||||||
|
handleModuleCall(event, event.source, domain, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (event.data.method === "queryUpdate") {
|
||||||
|
handleQueryUpdate(event);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unrecognized method, reject the query.
|
||||||
|
respondErr(
|
||||||
|
event,
|
||||||
|
event.source,
|
||||||
|
false,
|
||||||
|
"unrecognized method: " + event.data.method,
|
||||||
|
);
|
||||||
|
};
|
Loading…
Reference in New Issue