webvm/index.html
Yuri Iozzelli d0e3852b59 lazy load go wasm module only when logging in
To avoid waiting too much for the go module download and initialization,
and being considered an unwanted popup

Issue: the login window is blocked as a popup
2022-10-05 09:31:21 +02:00

423 lines
16 KiB
HTML

<!DOCTYPE html>
<html lang="en" style="height:100%;">
<head>
<!-- Google Tag Manager -->
<script>
(function (w, d, s, l, i) {w[l] = w[l] || []; w[l].push({'gtm.start': new Date().getTime(),event: 'gtm.js'});var f = d.getElementsByTagName(s)[0],j = d.createElement(s),dl = l != 'dataLayer' ? '&l=' + l : '';j.async = true;j.src ='https://www.googletagmanager.com/gtm.js?id=' + i + dl;f.parentNode.insertBefore(j, f);})(window, document, 'script', 'dataLayer', 'GTM-5GBJM42');
</script>
<!-- End Google Tag Manager -->
<meta charset="utf-8">
<title>WebVM</title>
<meta name="description" content="Welcome to WebVM - a server-less virtual Linux environment running fully client-side in HTML5/WebAssembly."
<meta name="keywords" content="WebVM, Bash, CheerpX, WebAssembly">
<meta property="og:title" content="WebVM" />
<meta property="og:type" content="website" />
<meta property="og:site_name" content="WebVM"/>
<meta property="og:url" content="/">
<meta property="og:image" content="https://webvm.io/assets/webvm.jpeg" />
<meta name="twitter:card" content="summary" />
<meta name="twitter:site" content="@leaningtech" />
<meta name="twitter:title" content="WebVM" />
<meta name="twitter:description" content="Welcome to WebVM - a server-less virtual Linux environment running fully client-side in HTML5/WebAssembly."
<meta name="twitter:image" content="https://webvm.io/assets/webvm.jpeg" />
<!-- Apple iOS web clip compatibility tags -->
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="application-name" content="WebVM" />
<meta name="apple-mobile-web-app-title" content="WebVM" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<link rel="shortcut icon" href="./favicon.ico">
<link rel="preconnect" href="https://fonts.gstatic.com">
<link rel="preload" as="style" href="https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@400;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="./xterm/xterm.css" />
<script>
window.networkInterface = {};
</script>
<script type="module">
import { State } from "/tun/tailscale_tun.js";
import { autoConf } from "/tun/tailscale_tun_auto.js";
let resolveLogin = null;
let loginPromise = new Promise((f,r) => {
resolveLogin = f;
});
const loginUrlCb = (url) => {
const a = document.getElementById("loginLink");
a.href = url;
a.target = "_blank";
const status = document.getElementById("networkStatus");
status.innerHTML = "Tailscale Login";
resolveLogin(url);
};
const stateUpdateCb = (state) => {
switch(state)
{
case State.NeedsLogin:
{
break;
}
case State.Running:
{
const a = document.getElementById("loginLink");
a.href = "https://login.tailscale.com/admin/machines";
break;
}
case State.Starting:
{
break;
}
case State.Stopped:
{
break;
}
case State.NoState:
{
break;
}
}
};
const netmapUpdateCb = (map) => {
const ip = map.self.addresses[0];
const status = document.getElementById("networkStatus");
status.innerHTML = "Ip: "+ip;
};
const { listen, connect, bind, up } = await autoConf({
loginUrlCb,
stateUpdateCb,
netmapUpdateCb,
});
window.networkInterface.bind = bind;
window.networkInterface.connect = connect;
window.networkInterface.listen = listen;
window.startTailscaleAndGetLogin = async () => {
const a = document.getElementById("loginLink");
a.onclick = null;
const status = document.getElementById("networkStatus");
status.innerHTML = "Downloading network code...";
const w = window.open("login.html", "_blank");
await up();
w.document.body.innerHTML = "Starting login...";
status.innerHTML = "Starting login...";
const url = await loginPromise;
w.location.href = url;
};
</script>
<script src="./xterm/xterm.js"></script>
<script src="./xterm/xterm-addon-fit.js"></script>
</head>
<body style="margin:0;height:100%;background:black;color:white;overflow:hidden; display:flex; flex-direction: column; justify-content: space-between; height: 100%;">
<!-- Google Tag Manager (noscript) -->
<noscript>
<iframe src="https://www.googletagmanager.com/ns.html?id=GTM-5GBJM42" height="0" width="0"
style="display:none;visibility:hidden"></iframe>
</noscript>
<!-- End Google Tag Manager (noscript) -->
<header style="flex-grow:0; flex-srink: 0;height:80px; width: 100%; margin: 2px 0 2px 0;">
<div style="display: flex; flex-direction: row; justify-content: space-between; width: 100%;">
<div style="display: flex; flex-direction: row;">
<img src="./assets/tower.png" style="margin-left: 10px; width: 70px; height: 80px;">
<div style=" margin-left: 10px; height: 100%; display: flex; align-items: center;">
<div style="color: white; font-family: montserrat; font-weight: 700; font-size: xxx-large;">WebVM</div>
</div>
</div>
<div style="display: flex; flex-direction: row; justify-content: space-before;">
<li style=" margin-right: 50px; height: 100%; display: flex; align-items: center;">
<a href="https://github.com/leaningtech/webvm" target="_blank">
<div style="color: white; font-family: montserrat; font-weight: 700; font-size: large;">Github</div>
</a>
</li>
<li style=" margin-right: 50px; height: 100%; display: flex; align-items: center;">
<a href="https://github.com/leaningtech/webvm/issues" target="_blank">
<div style="color: white; font-family: montserrat; font-weight: 700; font-size: large;">Issues</div>
</a>
</li>
<li style=" margin-right: 50px; height: 100%; display: flex; align-items: center;">
<a href="https://medium.com/p/40a60170b361" target="_blank">
<div style="color: white; font-family: montserrat; font-weight: 700; font-size: large;">About</div>
</a>
</li>
<li style=" margin-right: 50px; height: 100%; display: flex; align-items: center;">
<a id="loginLink" href="#" onclick="startTailscaleAndGetLogin()">
<div id="networkStatus" style="color: white; font-family: montserrat; font-weight: 700; font-size: large;">Tailscale Login</div>
</a>
</li>
</div>
</div>
</header>
<div style="flex-grow:0; flex-shrink: 0; height:1px; width: 100%; background-color: white;">
</div>
<main style="display: flex; flex-direction: row; justify-content: space-between; margin:0; height:100%;">
<div style="flex-grow:1; height:100%;display:inline-block;margin:0;" id="console">
</div>
</main>
<script>
//Utility namespace to group all functionality related to printing (both error and non error) messages
const color= "\x1b[1;35m";
const bold= "\x1b[1;37m";
const underline= "\x1b[94;4m";
const normal= "\x1b[0m";
var printOnTerm = {
getAsciiTitle: function ()
{
var title = [
color + " __ __ _ __ ____ __ " + normal,
color + " \\ \\ / /__| |_\\ \\ / / \\/ | " + normal,
color + " \\ \\/\\/ / -_) '_ \\ V /| |\\/| | " + normal,
color + " \\_/\\_/\\___|_.__/\\_/ |_| |_| " + normal,
];
return title;
},
getAsciiText: function ()
{
var text = [
" +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+",
" | |",
" | WebVM is a server-less virtual Linux environment running fully client-side |",
" | in HTML5/WebAssembly. |",
" | |",
" | In this demo, it runs an unmodified Debian distribution including many |",
" | native development toolchains. |",
" | |",
" | WebVM is powered by the CheerpX virtualization engine, and enables safe, |",
" | sandboxed client-side execution of x86 binaries on any browser. |",
" | |",
" | CheerpX includes an x86-to-WebAssembly JIT compiler, a virtual block-based |",
" | file system, and a Linux syscall emulator. |",
" | |",
" | For more information: " + underline + "https://medium.com/p/40a60170b361" + normal + " |",
" | |",
" | " +
underline + "GitHub" + normal + " | " +
underline + "Issues" + normal + " | " +
underline + "Gitter" + normal + " | " +
underline + "Twitter" + normal + " | " +
underline + "Latest News" + normal + " | " +
underline + "About CheerpX" + normal + " |",
" | |",
" +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+",
"",
" Welcome to WebVM (build CX_VERSION). If unsure, try these examples:",
"",
" python3 examples/python3/fibonacci.py ",
" gcc -o helloworld examples/c/helloworld.c && ./helloworld",
" objdump -d ./helloworld | less -M",
" vim examples/c/helloworld.c",
"",
];
return text;
},
getSharedArrayBufferMissingMessage: function ()
{
const text = [
"",
"",
color + "CheerpX could not start" + normal,
"",
"CheerpX depends on JavaScript's SharedArrayBuffer, that your browser",
" does not support.",
"",
"SharedArrayBuffer is currently enabled by default on recent",
" versions of Chrome, Edge, Firefox and Safari.",
"",
"",
"Give it a try from another browser!",
]
return text;
},
getErrorMessage: function (error_message)
{
const text = [
"",
"",
color + "CheerpX could not start" + normal,
"",
"CheerpX internal error message is:",
error_message,
"",
"",
"CheerpX is expected to work with recent desktop versions of Chrome, Edge, Firefox and Safari",
"",
"",
"Give it a try from a desktop version / another browser!",
]
return text;
},
printMessage: function (text) {
for (var i=0; i<text.length; i++)
{
term.write(text[i]);
term.write('\n');
}
},
printError: function (message)
{
this.printMessage(message);
term.write("\n\n");
function writeCustom(something)
{
term.write(something);
}
},
};
var consoleDiv = document.getElementById("console");
//xterm.js related logic
var term = new Terminal({cursorBlink:true,convertEol:true, fontFamily:"'Roboto Mono', monospace", fontWeight: 400, fontWeightBold: 700});
var fitAddon = new FitAddon.FitAddon();
term.loadAddon(fitAddon);
term.open(consoleDiv);
term.scrollToTop();
fitAddon.fit();
window.addEventListener("resize", function(ev){fitAddon.fit();}, false);
term.focus();
var cxReadFunc = null;
function writeData(buf)
{
term.write(new Uint8Array(buf));
}
function readData(str)
{
if(cxReadFunc == null)
return;
for(var i=0;i<str.length;i++)
cxReadFunc(str.charCodeAt(i));
}
term.onData(readData);
//Actual CheerpX and bash specific logic
function runBash()
{
const structure = {
name: "bash",
cmd: "/bin/bash",
args: ["--login"],
env: ["HOME=/home/user", "TERM=xterm", "USER=user", "SHELL=/bin/bash", "EDITOR=vim", "LANG=en_US.UTF-8", "LC_ALL=C"],
expectedPrompt: ">",
versionOpt: "--version",
comment_line: "#",
description_line: "The original Bourne Again SHell",
}
if (typeof SharedArrayBuffer === "undefined")
{
printOnTerm.printError(printOnTerm.getSharedArrayBufferMissingMessage());
return;
}
async function runTest(cx)
{
term.scrollToBottom();
async function cxLogAndRun(cheerpx, cmd, args, env)
{
await cheerpx.run(cmd, args, env);
printOnTerm.printMessage(" ");
}
cxReadFunc = cx.setCustomConsole(writeData, term.cols, term.rows);
function preventDefaults (e) {
e.preventDefault()
e.stopPropagation()
}
consoleDiv.addEventListener("dragover", preventDefaults, false);
consoleDiv.addEventListener("dragenter", preventDefaults, false);
consoleDiv.addEventListener("dragleave", preventDefaults, false);
consoleDiv.addEventListener("drop", preventDefaults, false);
var opts = {env:structure.env, cwd:"/home/user"};
while (true)
{
await cxLogAndRun(cx, structure.cmd, structure.args, opts);
}
}
function failCallback(err)
{
printOnTerm.printError(printOnTerm.getErrorMessage(err));
}
CheerpXApp.create({devices:[{type:"block",url:"https://127.0.0.1:8080/images/webvm_20220131.ext2",name:"block1"}],mounts:[{type:"ext2",dev:"block1",path:"/"},{type:"cheerpOS",dev:"/app",path:"/app"},{type:"cheerpOS",dev:"/str",path:"/data"},{type:"devs",dev:"",path:"/dev"}], networkInterface}).then(runTest, failCallback);
}
function initialMessage()
{
printOnTerm.printMessage(printOnTerm.getAsciiTitle());
printOnTerm.printMessage(["\n"]);
printOnTerm.printMessage(printOnTerm.getAsciiText());
term.registerLinkMatcher(/https:\/\/medium\.com\/p\/40a60170b361/, function(mouseEvent, matchedString) {
window.open(matchedString, "_blank")
});
const textArray = new Array(6);
const linksArray = new Array(6);
const rangesArray = new Array(6);
var last = 0;
const textLinkLine = " | GitHub | Issues | Gitter | Twitter | Latest News | About CheerpX |";
const lineWithLinks = 23;
function addLink(text, website)
{
var index = textLinkLine.indexOf(text);
const start_x = index+1;
const end_x = index + text.length;
rangesArray[last] = {start: {x:start_x, y:lineWithLinks}, end: {x:end_x, y:lineWithLinks}};
linksArray[last] = website;
last++;
}
addLink("GitHub", "https://github.com/leaningtech/webvm");
addLink("Issues", "https://github.com/leaningtech/webvm/issues");
addLink("Gitter", "https://gitter.im/leaningtech/cheerpx");
addLink("Twitter", "https://twitter.com/leaningtech");
addLink("Latest News", "https://medium.com/leaningtech");
addLink("About CheerpX", "https://leaningtech.com/cheerpx");
var provider = {
provideLinks(bufferLineNum, callback) {
switch(bufferLineNum)
{
case lineWithLinks:
{
callback([
{range: rangesArray[0], activate() {window.open('' + linksArray[0], '_blank');}},
{range: rangesArray[1], activate() {window.open('' + linksArray[1], '_blank');}},
{range: rangesArray[2], activate() {window.open('' + linksArray[2], '_blank');}},
{range: rangesArray[3], activate() {window.open('' + linksArray[3], '_blank');}},
{range: rangesArray[4], activate() {window.open('' + linksArray[4], '_blank');}},
{range: rangesArray[5], activate() {window.open('' + linksArray[5], '_blank');}},
]);
break;
}
}
}
};
term.registerLinkProvider(provider);
console.log("Welcome. We appreciate curiosity, but be warned that keeping the DevTools open causes significant performance degradation and crashes.");
}
initialMessage();
var script = document.createElement('script');
script.type = 'text/javascript';
var cxFile = "https://cheerpxdemos.leaningtech.com/publicdeploy/CX_VERSION/cx.js";
script.src = cxFile;
script.addEventListener("load", runBash, false);
document.head.appendChild(script);
</script>
</body>
</html>