Fixed the labels not being hidden in inoutdivs, made inoutdivs use a flexbox or table to avoid the use of a calc() and a transform(), updated to AES-256 GCM Native crypto from CryptoJS, added a migration page to migrate from CryptoJS, remove the Argon2 Compatibility thing, remove all backwards-compatibility measures, updated the changelog to be accurate, made NoScript cancel-able, made the inoutdiv dynamically scale, told people what a homeserver is, switched to argon2id from sha-3 and sha-512, add a PoW captcha using WASM, make a really long commit message.
If you are still reading this, I admire your dedication. I spent 8 hours and 33 minutes on this commit alone.
This commit is contained in:
parent
33f0517a9d
commit
05279a0811
|
@ -6,9 +6,8 @@
|
|||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<link rel="stylesheet" type="text/css" href="/static/css/style.css" />
|
||||
<link rel="stylesheet" type="text/css" href="/static/css/style.css"/>
|
||||
<script type="text/javascript" src="/static/js/hash-wasm.js"></script>
|
||||
<script type="text/javascript" src="/static/js/crypto-js.js"></script>
|
||||
<link rel="icon" href="/static/svg/favicon.svg">
|
||||
</head>
|
||||
|
||||
|
|
|
@ -14,14 +14,58 @@
|
|||
<body>
|
||||
<img src="/static/img/background.jpg" class="background" alt="">
|
||||
<div class="inoutdiv">
|
||||
<noscript>
|
||||
<style>
|
||||
.checkMark {
|
||||
width: inherit !important;
|
||||
height: inherit !important;
|
||||
display: block !important;
|
||||
position: fixed !important;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(calc(-50% - 125px), calc(-50% - 16px));
|
||||
}
|
||||
|
||||
.noContent {
|
||||
display: inherit;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.checkMark:checked ~ .content {
|
||||
display: inherit !important;
|
||||
}
|
||||
|
||||
.checkMark:checked ~ .noContent {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.checkMark:checked {
|
||||
display: none !important;
|
||||
}
|
||||
</style>
|
||||
</noscript>
|
||||
<input type="checkbox" class="checkMark">
|
||||
<div class="noContent">
|
||||
<h2 style="display: block !important;">Your web browser is unsupported</h2>
|
||||
<p>Please enable JavaScript in your web browser to continue</p>
|
||||
<p style="margin-bottom: 135px;">False alarm? Click the checkmark to ignore this warning.</p>
|
||||
</div>
|
||||
<div class="content">
|
||||
<h2>Alternative servers</h2>
|
||||
<p>Enter your custom homeserver URL <a href="/">What does this mean?</a></p>
|
||||
|
||||
<span id="inputNameBox" style="margin-right: 5px;color: var(--text-color);">Server URL:</span><input type="text" placeholder="https://example.org" id="homeserverBox"><br>
|
||||
<button id="changeButton">Change</button><button class="nonimportant" id="backButton">Cancel</button><br><br>
|
||||
|
||||
<p id="statusBox">Loading...</p>
|
||||
<p>Enter your custom homeserver URL</p>
|
||||
<div class="inputContainer" id="inputContainer">
|
||||
<div class="vAlign"><span id="inputNameBox">URL:</span></div>
|
||||
<input type="text" placeholder="https://example.org" id="homeserverBox"><br>
|
||||
</div>
|
||||
<p>Please put in the URL in standard format; https://, http://, etc.</p>
|
||||
<button id="changeButton" class="clickButton">Change</button>
|
||||
<button class="clickButton nonimportant" id="backButton">Cancel</button><br><br>
|
||||
<p>A homeserver is the server that your notes and account are stored on.</p>
|
||||
<p id="statusBox">Loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript" src="/static/js/homeserver.js"></script>
|
||||
|
|
|
@ -15,27 +15,61 @@
|
|||
<img src="/static/img/background.jpg" class="background" alt="">
|
||||
<div class="inoutdiv">
|
||||
<noscript>
|
||||
<h2 style="display: block !important;">Your web browser is unsupported</h2>
|
||||
<p style="margin-bottom: 135px;">Please enable JavaScript in your web browser to continue</p>
|
||||
<style>
|
||||
h2, button, br {
|
||||
.checkMark {
|
||||
width: inherit !important;
|
||||
height: inherit !important;
|
||||
display: block !important;
|
||||
position: fixed !important;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(calc(-50% - 125px), calc(-50% - 16px));
|
||||
}
|
||||
|
||||
.noContent {
|
||||
display: inherit;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.checkMark:checked ~ .content {
|
||||
display: inherit !important;
|
||||
}
|
||||
|
||||
.checkMark:checked ~ .noContent {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.checkMark:checked {
|
||||
display: none !important;
|
||||
}
|
||||
</style>
|
||||
</noscript>
|
||||
<input type="checkbox" class="checkMark">
|
||||
<div class="noContent">
|
||||
<h2 style="display: block !important;">Your web browser is unsupported</h2>
|
||||
<p>Please enable JavaScript in your web browser to continue</p>
|
||||
<p style="margin-bottom: 135px;">False alarm? Click the checkmark to ignore this warning.</p>
|
||||
</div>
|
||||
<div class="content">
|
||||
<h2>Sign in</h2>
|
||||
<p id="statusBox"></p>
|
||||
<span id="inputNameBox" style="margin-right: 10px;color: var(--text-color);"></span>
|
||||
<div class="inputContainer" id="inputContainer">
|
||||
<div class="vAlign"><span id="inputNameBox"></span></div>
|
||||
<input id="usernameBox" class="hidden" type="text" placeholder="Enter your username">
|
||||
<input id="passwordBox" class="hidden" type="password" placeholder="Enter your password">
|
||||
</div>
|
||||
<button class="clickButton" id="signupButton">Next</button>
|
||||
<button id="backButton" class="clickButton hidden nonimportant">Back</button>
|
||||
<button id="opButton" class="clickButton nonimportant">Create account</button>
|
||||
<br><br>
|
||||
<div style="display: flex;"><p class="hidden" id="homeserver">Your homeserver is loading... </p><div style="display: flex;flex-direction: column;justify-content: center;"><a class="hidden" href="/homeserver">Change</a></div></div>
|
||||
<a class="iconbutton" title="Change homeserver" href="/homeserver/"><img src="/static/svg/server.svg"></a><br><br>
|
||||
<a class="iconbutton" title="Change homeserver" href="/homeserver/"><img src="/static/svg/server.svg" alt="Homeservers"></a><br><br>
|
||||
<a href="/privacy/">Privacy & Terms</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript" src="../static/js/login.js"></script>
|
||||
</body>
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Burgernotes</title>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<link rel="stylesheet" type="text/css" href="/static/css/style.css"/>
|
||||
<script type="text/javascript" src="/static/js/hash-wasm.js"></script>
|
||||
<script type="text/javascript" src="/static/js/crypto-js.js"></script>
|
||||
<script type="text/javascript" src="/static/js/migrate.js"></script>
|
||||
<link rel="icon" href="/static/svg/favicon.svg">
|
||||
</head>
|
||||
<body>
|
||||
<img src="/static/img/background.jpg" class="background" alt="">
|
||||
<div class="inoutdiv">
|
||||
<noscript>
|
||||
<style>
|
||||
.checkMark {
|
||||
width: inherit !important;
|
||||
height: inherit !important;
|
||||
display: block !important;
|
||||
position: fixed !important;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(calc(-50% - 125px), calc(-50% - 16px));
|
||||
}
|
||||
|
||||
.noContent {
|
||||
display: inherit;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.checkMark:checked ~ .content {
|
||||
display: inherit !important;
|
||||
}
|
||||
|
||||
.checkMark:checked ~ .noContent {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.checkMark:checked {
|
||||
display: none !important;
|
||||
}
|
||||
</style>
|
||||
</noscript>
|
||||
<input type="checkbox" class="checkMark">
|
||||
<div class="noContent">
|
||||
<h2 style="display: block !important;">Your web browser is unsupported</h2>
|
||||
<p>Please enable JavaScript in your web browser to continue</p>
|
||||
<p style="margin-bottom: 135px;">False alarm? Click the checkmark to ignore this warning.</p>
|
||||
</div>
|
||||
<div class="content">
|
||||
<h2 id="title">Burgernotes Migrator</h2>
|
||||
<p id="information">Welcome to the Burgernotes Migration wizard! Before we begin migration, there are a few things you should know.</p>
|
||||
<button onclick="buttonClick()" class="clickButton">Continue</button>
|
||||
<button id="backButton" onclick="back()" class="clickButton nonimportant hidden">Back</button>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -8,23 +8,72 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<link rel="stylesheet" type="text/css" href="/static/css/style.css" />
|
||||
<script src="/static/js/hash-wasm.js"></script>
|
||||
<script src="/static/js/wasm_exec.js"></script>
|
||||
<link rel="icon" href="/static/svg/favicon.svg">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<img src="/static/img/background.jpg" class="background" alt="">
|
||||
<div class="inoutdiv">
|
||||
<noscript>
|
||||
<style>
|
||||
.checkMark {
|
||||
width: inherit !important;
|
||||
height: inherit !important;
|
||||
display: block !important;
|
||||
position: fixed !important;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(calc(-50% - 125px), calc(-50% - 16px));
|
||||
}
|
||||
|
||||
.noContent {
|
||||
display: inherit;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.checkMark:checked ~ .content {
|
||||
display: inherit !important;
|
||||
}
|
||||
|
||||
.checkMark:checked ~ .noContent {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.checkMark:checked {
|
||||
display: none !important;
|
||||
}
|
||||
</style>
|
||||
</noscript>
|
||||
<input type="checkbox" class="checkMark">
|
||||
<div class="noContent">
|
||||
<h2 style="display: block !important;">Your web browser is unsupported</h2>
|
||||
<p>Please enable JavaScript in your web browser to continue</p>
|
||||
<p style="margin-bottom: 135px;">False alarm? Click the checkmark to ignore this warning.</p>
|
||||
</div>
|
||||
<div class="content">
|
||||
<h2>Create a Burgernotes Account</h2>
|
||||
<p>Get started by picking a username and password</p>
|
||||
<p style="color: #b3261e; font-size: 14px; margin-bottom: 2px; margin-top: 1px;" id="statusBox"></p>
|
||||
<span id="inputNameBox" style="margin-right: 10px;color: var(--text-color);">Username:</span>
|
||||
<input id="usernameBox" type="text" placeholder="Username">
|
||||
<span id="inputNameBox" style="margin-right: 13px;color: var(--text-color);">Password: </span>
|
||||
<input id="passwordBox" type="password" placeholder="Password"><br>
|
||||
<table id="inputContainer">
|
||||
<tr>
|
||||
<td><span id="inputNameBox">Username:</span></td>
|
||||
<td class="inputBox"><input id="usernameBox" type="text" placeholder="Username"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span id="inputPasswordBox">Password: </span></td>
|
||||
<td class="inputBox"><input id="passwordBox" type="password" placeholder="Password"><br></td>
|
||||
</tr>
|
||||
</table>
|
||||
<br>
|
||||
<button class="clickButton" id="signupButton">Create account</button><button id="opButton" class="clickButton nonimportant">Already have an account</button><br><br>
|
||||
<div style="display: flex;"><p class="hidden" id="homeserver">Your homeserver is loading... </p><div style="display: flex;flex-direction: column;justify-content: center;"><a class="hidden" href="/homeserver">Change</a></div></div>
|
||||
<a class="iconbutton" title="Change homeserver" href="/homeserver/"><img src="/static/svg/server.svg"></a>
|
||||
<a class="iconbutton" title="Change homeserver" href="/homeserver/"><img src="/static/svg/server.svg" alt="Homeservers"></a>
|
||||
<a href="/privacy/">Privacy & Terms</a>
|
||||
</div>
|
||||
<script type="text/javascript" src="/static/js/signup.js"></script>
|
||||
</div>
|
||||
<script src="/static/js/signup.js"></script>
|
||||
</body>
|
||||
|
|
|
@ -652,22 +652,44 @@ button:hover {
|
|||
transform: translate(-50%, -50%);
|
||||
position: fixed;
|
||||
min-width: 585px;
|
||||
height: 270px;
|
||||
max-height: 270px;
|
||||
min-height: 270px;
|
||||
border-radius: 16px;
|
||||
padding: 35px;
|
||||
background-color: var(--portal-background-color);
|
||||
}
|
||||
|
||||
.noContent {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.inoutdiv .checkMark {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.inoutdiv h2 {
|
||||
font-family: "Space Grotesk", sans-serif;
|
||||
font-optical-sizing: auto;
|
||||
}
|
||||
|
||||
.inoutdiv input {
|
||||
width: calc(100% - 120px);
|
||||
height: 30px;
|
||||
.inoutdiv .inputBox {
|
||||
width: 100%
|
||||
}
|
||||
|
||||
.inoutdiv .inputContainer {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
margin-right: 30px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.inoutdiv table {
|
||||
margin-right: 30px;
|
||||
}
|
||||
|
||||
.inoutdiv input {
|
||||
width: 100%;
|
||||
margin-left: 10px;
|
||||
height: 30px;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
|
||||
|
|
|
@ -19,10 +19,12 @@ function showElements(yesorno) {
|
|||
if (!yesorno) {
|
||||
homeserverBox.classList.add("hidden")
|
||||
changeButton.classList.add("hidden")
|
||||
inputContainer.classList.add("hidden")
|
||||
}
|
||||
else {
|
||||
homeserverBox.classList.remove("hidden")
|
||||
changeButton.classList.remove("hidden")
|
||||
inputContainer.classList.remove("hidden")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ if (remote == null) {
|
|||
remote = "https://notes.hectabit.org"
|
||||
}
|
||||
|
||||
let inputContainer = document.getElementById("inputContainer")
|
||||
let usernameBox = document.getElementById("usernameBox")
|
||||
let passwordBox = document.getElementById("passwordBox")
|
||||
let statusBox = document.getElementById("statusBox")
|
||||
|
@ -20,53 +21,16 @@ let inputNameBox = document.getElementById("inputNameBox")
|
|||
let backButton = document.getElementById("backButton")
|
||||
let opButton = document.getElementById("opButton")
|
||||
|
||||
async function loginFetch(username, password, changePass, newPass) {
|
||||
if (localStorage.getItem("legacy") !== true) {
|
||||
async function loginFetch(username, password, modern) {
|
||||
return await fetch(remote + "/api/login", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
username: username,
|
||||
password: password,
|
||||
modern: modern
|
||||
}),
|
||||
headers: {
|
||||
"Content-Type": "application/json; charset=UTF-8",
|
||||
"X-Burgernotes-Version": "200"
|
||||
}
|
||||
})
|
||||
} else {
|
||||
let passwordChange, newPassChecked
|
||||
if (changePass) {
|
||||
passwordChange = "yes"
|
||||
newPassChecked = newPass
|
||||
} else {
|
||||
passwordChange = "no"
|
||||
newPassChecked = password
|
||||
}
|
||||
return await fetch(remote + "/api/login", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
username: username,
|
||||
password: password,
|
||||
passwordchange: passwordChange,
|
||||
newpass: newPassChecked
|
||||
}),
|
||||
headers: {
|
||||
"Content-Type": "application/json; charset=UTF-8",
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function addLegacyPassword(secretKey, password) {
|
||||
return await fetch(remote + "/api/v2/addlegacypassword", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
secretKey: secretKey,
|
||||
legacyPassword: password,
|
||||
}),
|
||||
headers: {
|
||||
"Content-Type": "application/json; charset=UTF-8",
|
||||
"X-Burgernotes-Version": "200"
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -80,24 +44,23 @@ async function migrateLegacyPassword(secretKey, password) {
|
|||
}),
|
||||
headers: {
|
||||
"Content-Type": "application/json; charset=UTF-8",
|
||||
"X-Burgernotes-Version": "200"
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function hashpassold(pass) {
|
||||
async function hashPass(pass) {
|
||||
return await hashwasm.argon2id({
|
||||
password: pass,
|
||||
salt: await hashwasm.sha512(pass),
|
||||
salt: new TextEncoder().encode("I munch Burgers!!"),
|
||||
parallelism: 1,
|
||||
iterations: 256,
|
||||
memorySize: 512,
|
||||
iterations: 32,
|
||||
memorySize: 19264,
|
||||
hashLength: 32,
|
||||
outputType: "encoded"
|
||||
outputType: "hex"
|
||||
})
|
||||
}
|
||||
|
||||
async function hashpass(pass) {
|
||||
async function hashPassLegacy(pass) {
|
||||
let key = pass
|
||||
for (let i = 0; i < 128; i++) {
|
||||
key = await hashwasm.sha3(key)
|
||||
|
@ -111,24 +74,28 @@ inputNameBox.innerText = "Username:"
|
|||
let currentInputType = 0
|
||||
|
||||
function showInput(inputType) {
|
||||
if (inputType === 0) {
|
||||
switch (inputType) {
|
||||
case 0:
|
||||
usernameBox.classList.remove("hidden")
|
||||
passwordBox.classList.add("hidden")
|
||||
backButton.classList.add("hidden")
|
||||
opButton.classList.remove("hidden")
|
||||
inputContainer.classList.remove("hidden")
|
||||
inputNameBox.innerText = "Username:"
|
||||
statusBox.innerText = "Sign in with your Burgernotes account"
|
||||
currentInputType = 0
|
||||
} else if (inputType === 1) {
|
||||
break
|
||||
case 1:
|
||||
usernameBox.classList.add("hidden")
|
||||
passwordBox.classList.remove("hidden")
|
||||
backButton.classList.remove("hidden")
|
||||
inputContainer.classList.remove("hidden")
|
||||
opButton.classList.add("hidden")
|
||||
inputNameBox.innerText = "Password:"
|
||||
currentInputType = 1
|
||||
} else if (inputType === 2) {
|
||||
usernameBox.classList.add("hidden")
|
||||
passwordBox.classList.add("hidden")
|
||||
break
|
||||
case 2:
|
||||
inputContainer.classList.add("hidden")
|
||||
signupButton.classList.add("hidden")
|
||||
backButton.classList.add("hidden")
|
||||
inputNameBox.classList.add("hidden")
|
||||
|
@ -138,8 +105,8 @@ function showInput(inputType) {
|
|||
}
|
||||
}
|
||||
|
||||
function showElements(yesorno) {
|
||||
if (!yesorno) {
|
||||
function showElements(show) {
|
||||
if (!show) {
|
||||
usernameBox.classList.add("hidden")
|
||||
passwordBox.classList.add("hidden")
|
||||
signupButton.classList.add("hidden")
|
||||
|
@ -186,39 +153,61 @@ signupButton.addEventListener("click", () => {
|
|||
|
||||
showInput(2)
|
||||
showElements(true)
|
||||
statusBox.innerText = "Signing in..."
|
||||
statusBox.innerText = "Hashing password..."
|
||||
|
||||
const hashedPass = await hashpass(password)
|
||||
const login = await loginFetch(username, hashedPass, false, "")
|
||||
const hashedPass = await hashPass(password)
|
||||
const login = await loginFetch(username, hashedPass, true)
|
||||
const loginData = await login.json()
|
||||
if (login.status === 401) {
|
||||
// Trying hashpassold
|
||||
const loginOld = await loginFetch(username, await hashpassold(password), true, hashedPass)
|
||||
if (loginData["migrated"] !== true) {
|
||||
statusBox.innerText = "Migrating to Burgernotes 2.0..."
|
||||
const loginOld = await loginFetch(username, await hashPassLegacy(password), false)
|
||||
const loginDataOld = await loginOld.json()
|
||||
if (loginOld.status === 401) {
|
||||
statusBox.innerText = "Username or password incorrect!"
|
||||
showInput(1)
|
||||
showElements(true)
|
||||
} else if (loginOld.status === 200) {
|
||||
statusBox.innerText = "Setting up encryption keys..."
|
||||
localStorage.setItem("DONOTSHARE-secretkey", loginDataOld["key"])
|
||||
localStorage.setItem("DONOTSHARE-password", await hashwasm.sha512(password))
|
||||
if (loginDataOld["legacyPasswordNeeded"] === true) {
|
||||
await addLegacyPassword(username, await hashpass(await hashpassold(password)))
|
||||
}
|
||||
localStorage.setItem("DONOTSHARE-password", await hashwasm.argon2id({
|
||||
password: password,
|
||||
salt: new TextEncoder().encode("I love Burgernotes!"),
|
||||
parallelism: 1,
|
||||
iterations: 32,
|
||||
memorySize: 19264,
|
||||
hashLength: 32,
|
||||
outputType: "hex"
|
||||
}))
|
||||
await migrateLegacyPassword(loginDataOld["key"], hashedPass)
|
||||
window.location.replace("/app/")
|
||||
statusBox.innerText = "Welcome back!"
|
||||
await new Promise(r => setTimeout(r, 200))
|
||||
window.location.href = "/app/"
|
||||
} else {
|
||||
statusBox.innerText = loginDataOld["error"]
|
||||
showInput(1)
|
||||
showElements(true)
|
||||
}
|
||||
} else if (login.status === 200) {
|
||||
localStorage.setItem("DONOTSHARE-secretkey", loginData["key"])
|
||||
localStorage.setItem("DONOTSHARE-password", await hashwasm.sha512(password))
|
||||
if (loginData["legacyPasswordNeeded"] === true) {
|
||||
await addLegacyPassword(username, await hashpass(await hashpassold(password)))
|
||||
} else {
|
||||
statusBox.innerText = "Username or password incorrect!"
|
||||
showInput(1)
|
||||
showElements(true)
|
||||
}
|
||||
window.location.replace("/app/")
|
||||
} else if (login.status === 200) {
|
||||
statusBox.innerText = "Setting up encryption keys..."
|
||||
localStorage.setItem("DONOTSHARE-secretkey", loginData["key"])
|
||||
localStorage.setItem("DONOTSHARE-password", await hashwasm.argon2id({
|
||||
password: password,
|
||||
salt: new TextEncoder().encode("I love Burgernotes!"),
|
||||
parallelism: 1,
|
||||
iterations: 32,
|
||||
memorySize: 19264,
|
||||
hashLength: 32,
|
||||
outputType: "hex"
|
||||
}))
|
||||
statusBox.innerText = "Welcome back!"
|
||||
await new Promise(r => setTimeout(r, 200))
|
||||
window.location.href = "/app/"
|
||||
} else {
|
||||
statusBox.innerText = loginData["error"]
|
||||
showInput(1)
|
||||
|
|
|
@ -1,11 +1,6 @@
|
|||
// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-3.0
|
||||
|
||||
if (localStorage.getItem("DONOTSHARE-secretkey") === null) {
|
||||
window.location.replace("/login")
|
||||
document.body.innerHTML = "Redirecting..."
|
||||
throw new Error();
|
||||
}
|
||||
if (localStorage.getItem("DONOTSHARE-password") === null) {
|
||||
if (localStorage.getItem("DONOTSHARE-secretkey") === null || localStorage.getItem("DONOTSHARE-password") === null) {
|
||||
window.location.replace("/login")
|
||||
document.body.innerHTML = "Redirecting..."
|
||||
throw new Error();
|
||||
|
@ -17,7 +12,10 @@ if (remote == null) {
|
|||
remote = "https://notes.hectabit.org"
|
||||
}
|
||||
|
||||
function formatBytes(a, b = 2) { if (!+a) return "0 Bytes"; const c = 0 > b ? 0 : b, d = Math.floor(Math.log(a) / Math.log(1000)); return `${parseFloat((a / Math.pow(1000, d)).toFixed(c))} ${["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"][d]}` }
|
||||
function formatBytes(a, b = 2) {
|
||||
if (!+a) return "0 Bytes"; const c = 0 > b ? 0 : b, d = Math.floor(Math.log(a) / Math.log(1000));
|
||||
return `${parseFloat((a / Math.pow(1000, d)).toFixed(c))} ${["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"][d]}`
|
||||
}
|
||||
|
||||
let secretkey = localStorage.getItem("DONOTSHARE-secretkey")
|
||||
let password = localStorage.getItem("DONOTSHARE-password")
|
||||
|
@ -61,7 +59,6 @@ let textPlusBox = document.getElementById('textPlusBox');
|
|||
let textMinusBox = document.getElementById('textMinusBox');
|
||||
let wordCountBox = document.getElementById('wordCountBox');
|
||||
let removeBox = document.getElementById("removeBox")
|
||||
let importFile = document.getElementById("importFile")
|
||||
let importFileConfirm = document.getElementById("importFileConfirm")
|
||||
|
||||
let selectedNote = 0
|
||||
|
@ -71,39 +68,61 @@ let indiv = false
|
|||
let mobile = false
|
||||
let selectLatestNote = false
|
||||
|
||||
function arrayBufferToBase64(buffer) {
|
||||
const uint8Array = new Uint8Array(buffer);
|
||||
return btoa(String.fromCharCode.apply(null, uint8Array))
|
||||
}
|
||||
|
||||
function base64ToArrayBuffer(base64) {
|
||||
const binaryString = atob(base64);
|
||||
const length = binaryString.length;
|
||||
const buffer = new ArrayBuffer(length);
|
||||
const uint8Array = new Uint8Array(buffer);
|
||||
for (let i = 0; i < length; i++) {
|
||||
uint8Array[i] = binaryString.charCodeAt(i);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
async function getKey() {
|
||||
let password = localStorage.getItem("DONOTSHARE-password")
|
||||
let cryptoKey = await window.crypto.subtle.importKey("raw", new TextEncoder().encode(password), "PBKDF2", false, ["deriveBits", "deriveKey"])
|
||||
let salt = new TextEncoder().encode("I love Burgernotes!")
|
||||
let cryptoKey = await window.crypto.subtle.importKey("raw", new TextEncoder().encode(password), "PBKDF2", false, ["deriveBits", "deriveKey"])
|
||||
return await window.crypto.subtle.deriveKey({
|
||||
name: "PBKDF2",
|
||||
salt,
|
||||
iterations: 100000,
|
||||
iterations: 1,
|
||||
hash: "SHA-512"
|
||||
}, cryptoKey, {name: "AES-GCM", length: 256}, true, ["encrypt", "decrypt"])
|
||||
}
|
||||
|
||||
function encrypt(text) {
|
||||
getKey()
|
||||
.then((key) => {
|
||||
async function encrypt(text) {
|
||||
let cryptoKey = await getKey()
|
||||
let iv = window.crypto.getRandomValues(new Uint8Array(12))
|
||||
window.crypto.subtle.encrypt({
|
||||
let encrypted = await window.crypto.subtle.encrypt({
|
||||
name: "AES-GCM",
|
||||
iv: iv
|
||||
}, key, new TextEncoder().encode(text))
|
||||
.then((encrypted) => {
|
||||
}, cryptoKey, new TextEncoder().encode(text))
|
||||
return btoa(JSON.stringify({
|
||||
encrypted: encrypted,
|
||||
iv: iv
|
||||
encrypted: arrayBufferToBase64(encrypted),
|
||||
iv: arrayBufferToBase64(iv)
|
||||
}))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function decrypt(encrypted) {
|
||||
getKey()
|
||||
.then((key) => {
|
||||
})
|
||||
async function decrypt(encrypted) {
|
||||
if (encrypted === "") {
|
||||
return ""
|
||||
} else {
|
||||
let cryptoKey = await getKey()
|
||||
let jsonData = JSON.parse(atob(encrypted))
|
||||
let encryptedData = base64ToArrayBuffer(jsonData.encrypted)
|
||||
let iv = base64ToArrayBuffer(jsonData.iv)
|
||||
let decrypted = await window.crypto.subtle.decrypt({
|
||||
name: "AES-GCM",
|
||||
iv: iv
|
||||
}, cryptoKey, encryptedData)
|
||||
return new TextDecoder().decode(decrypted)
|
||||
}
|
||||
}
|
||||
|
||||
function handleGesture() {
|
||||
|
@ -136,7 +155,9 @@ function handleGesture() {
|
|||
document.addEventListener("DOMContentLoaded", function() {
|
||||
pell.init({
|
||||
element: pellAttacher,
|
||||
onChange: html => console.log(html),
|
||||
onChange: function(html) {
|
||||
// Having a nice day? This does nothing.
|
||||
},
|
||||
defaultParagraphSeparator: 'br',
|
||||
styleWithCSS: false,
|
||||
classes: {
|
||||
|
@ -502,7 +523,6 @@ document.addEventListener("DOMContentLoaded", function() {
|
|||
} else {
|
||||
closeErrorButton.classList.remove("hidden")
|
||||
const data = await response.json()
|
||||
console.log(data)
|
||||
displayError(data["error"])
|
||||
}
|
||||
}
|
||||
|
@ -638,8 +658,16 @@ document.addEventListener("DOMContentLoaded", function() {
|
|||
async function doStuff() {
|
||||
let responseData = await response.json()
|
||||
|
||||
let bytes = CryptoJS.AES.decrypt(responseData["content"], password);
|
||||
let cleanedHTML = bytes.toString(CryptoJS.enc.Utf8).replace(/<(?!\/?(h1|h2|br|img|blockquote|ol|li|b|i|u|strike|p|pre|ul|hr|a)\b)[^>]*>/gi, '')
|
||||
let htmlNote
|
||||
try {
|
||||
htmlNote = await decrypt(responseData["content"])
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
console.log(responseData)
|
||||
}
|
||||
|
||||
console.log(htmlNote)
|
||||
let cleanedHTML = htmlNote.replace(/<(?!\/?(h1|h2|br|img|blockquote|ol|li|b|i|u|strike|p|pre|ul|hr|a)\b)[^>]*>/gi, '(potential XSS tag was here)')
|
||||
noteBox.innerHTML = cleanedHTML.replace("\n", "<br>")
|
||||
|
||||
updateWordCount()
|
||||
|
@ -647,21 +675,19 @@ document.addEventListener("DOMContentLoaded", function() {
|
|||
noteBox.addEventListener("input", () => {
|
||||
updateWordCount()
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(() => {
|
||||
let preEncryptedTitle = noteBox.innerText
|
||||
timer = setTimeout(async () => {
|
||||
let preEncryptedTitle = "New note"
|
||||
|
||||
if (noteBox.innerText.substring(0, noteBox.innerText.indexOf("\n")) !== "") {
|
||||
preEncryptedTitle = noteBox.innerText.substring(0, noteBox.innerText.indexOf("\n"));
|
||||
if (noteBox.innerText !== "") {
|
||||
preEncryptedTitle = new RegExp('(.+?)(?=\n)|[\s\S]*?(\S+)(?=\n)').exec(noteBox.innerText)[0]
|
||||
}
|
||||
|
||||
preEncryptedTitle = truncateString(preEncryptedTitle, 15)
|
||||
document.getElementById(nameithink).innerText = preEncryptedTitle
|
||||
|
||||
let encryptedText = CryptoJS.AES.encrypt(noteBox.innerHTML, password).toString();
|
||||
let encryptedTitle = CryptoJS.AES.encrypt(preEncryptedTitle, password).toString();
|
||||
|
||||
console.log(encryptedTitle)
|
||||
console.log(encryptedText)
|
||||
console.log(noteBox.innerHTML)
|
||||
let encryptedText = await encrypt(noteBox.innerHTML)
|
||||
let encryptedTitle = await encrypt(preEncryptedTitle)
|
||||
|
||||
if (selectedNote === nameithink) {
|
||||
fetch(remote + "/api/editnote", {
|
||||
|
@ -694,7 +720,6 @@ document.addEventListener("DOMContentLoaded", function() {
|
|||
}
|
||||
|
||||
function updateNotes() {
|
||||
console.log("Notes updated")
|
||||
fetch(remote + "/api/listnotes", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
|
@ -720,15 +745,17 @@ document.addEventListener("DOMContentLoaded", function() {
|
|||
for (let i in responseData) {
|
||||
noteData = responseData[i]
|
||||
|
||||
let bytes = CryptoJS.AES.decrypt(noteData["title"], password);
|
||||
noteData["title"] = bytes.toString(CryptoJS.enc.Utf8)
|
||||
try {
|
||||
noteData["title"] = await decrypt(noteData["title"])
|
||||
} catch (e) {
|
||||
location.href = "/migrate"
|
||||
}
|
||||
|
||||
if (noteData["id"] > highestID) {
|
||||
highestID = noteData["id"]
|
||||
}
|
||||
|
||||
decryptedResponseData.push(noteData)
|
||||
console.log(noteData)
|
||||
}
|
||||
|
||||
document.querySelectorAll(".noteButton").forEach((el) => el.remove());
|
||||
|
@ -739,11 +766,7 @@ document.addEventListener("DOMContentLoaded", function() {
|
|||
noteButton.classList.add("noteButton")
|
||||
notesDiv.append(noteButton)
|
||||
|
||||
console.log(noteData["title"])
|
||||
|
||||
if (noteData["title"] === "") {
|
||||
console.log(noteData["title"])
|
||||
console.log("case")
|
||||
noteData["title"] = "New note"
|
||||
}
|
||||
|
||||
|
@ -787,10 +810,9 @@ document.addEventListener("DOMContentLoaded", function() {
|
|||
|
||||
updateNotes()
|
||||
|
||||
newNote.addEventListener("click", () => {
|
||||
newNote.addEventListener("click", async () => {
|
||||
let noteName = "New note"
|
||||
selectLatestNote = true
|
||||
console.log(selectLatestNote)
|
||||
|
||||
// create fake item
|
||||
document.querySelectorAll(".noteButton").forEach((el) => el.classList.remove("selected"));
|
||||
|
@ -802,7 +824,8 @@ document.addEventListener("DOMContentLoaded", function() {
|
|||
noteButton.classList.add("selected")
|
||||
noteBox.click()
|
||||
|
||||
let encryptedName = CryptoJS.AES.encrypt(noteName, password).toString(CryptoJS.enc.Utf8);
|
||||
let encryptedName
|
||||
encryptedName = await encrypt(noteName)
|
||||
|
||||
fetch(remote + "/api/newnote", {
|
||||
method: "POST",
|
||||
|
@ -851,22 +874,20 @@ document.addEventListener("DOMContentLoaded", function() {
|
|||
for (let i in responseData) {
|
||||
exportNotes.innerText = "Decrypting " + i + "/" + noteCount
|
||||
|
||||
let bytes = CryptoJS.AES.decrypt(responseData[i]["title"], password);
|
||||
responseData[i]["title"] = bytes.toString(CryptoJS.enc.Utf8)
|
||||
|
||||
let bytesd = CryptoJS.AES.decrypt(responseData[i]["content"], password);
|
||||
responseData[i]["content"] = bytesd.toString(CryptoJS.enc.Utf8)
|
||||
try {
|
||||
responseData[i]["title"] = await decrypt(responseData[i]["title"])
|
||||
responseData[i]["content"] = await decrypt(responseData[i]["content"])
|
||||
} catch (e) {
|
||||
location.href = "/migrate"
|
||||
}
|
||||
}
|
||||
return responseData
|
||||
}
|
||||
|
||||
async function importNotes(plaintextNotes) {
|
||||
for (let i in plaintextNotes) {
|
||||
let originalTitle = plaintextNotes[i]["title"];
|
||||
plaintextNotes[i]["title"] = CryptoJS.AES.encrypt(originalTitle, password).toString();
|
||||
|
||||
let originalContent = plaintextNotes[i]["content"];
|
||||
plaintextNotes[i]["content"] = CryptoJS.AES.encrypt(originalContent, password).toString();
|
||||
plaintextNotes[i]["title"] = await encrypt(plaintextNotes[i]["title"])
|
||||
plaintextNotes[i]["content"] = await encrypt(plaintextNotes[i]["content"])
|
||||
}
|
||||
let importNotesFetch = await fetch(remote + "/api/importnotes", {
|
||||
method: "POST",
|
||||
|
@ -948,7 +969,7 @@ document.addEventListener("DOMContentLoaded", function() {
|
|||
});
|
||||
|
||||
if (firstNewVersion()) {
|
||||
displayError("What's new in Burgernotes 2.0?\nRestyled client\nAdded changing passwords\nAdded importing notes")
|
||||
displayError("What's new in Burgernotes 2.0?\nRestyled client\nAdded changing passwords\nAdded importing notes\nChange the use of CryptoJS to Native AES GCM\nUse Argon2ID for hashing rather than the SHA family\nAdded a Proof-Of-Work CAPTCHA during signup\nMade the signup and login statuses more descriptive\nFixed various bugs and issues\nAdded markdown notes\nAdded support for uploading photos\nImproved privacy policy to be clearer about what is and isn't added\nRemoved some useless uses of cookies and replaced with localStorage\nFixed the privacy policy not redirecting correctly\nAdded a list of native clients\nMade the client support LibreJS and therefore GNU Icecat")
|
||||
}
|
||||
|
||||
checknetwork()
|
||||
|
|
|
@ -0,0 +1,191 @@
|
|||
// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-3.0
|
||||
|
||||
if (localStorage.getItem("DONOTSHARE-secretkey") === null || localStorage.getItem("DONOTSHARE-password") === null) {
|
||||
window.location.replace("/login")
|
||||
document.body.innerHTML = "Redirecting..."
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
let remote = localStorage.getItem("homeserverURL")
|
||||
if (remote == null) {
|
||||
localStorage.setItem("homeserverURL", "https://notes.hectabit.org")
|
||||
remote = "https://notes.hectabit.org"
|
||||
}
|
||||
|
||||
let notesPlainText = ""
|
||||
let information
|
||||
let backButton
|
||||
let titleBox
|
||||
let fileInput
|
||||
let inputTypeGlobal = 0
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
information = document.getElementById("information")
|
||||
backButton = document.getElementById("backButton")
|
||||
titleBox = document.getElementById("title")
|
||||
fileInput = document.getElementById("fileInput")
|
||||
})
|
||||
|
||||
function showInput(inputType) {
|
||||
inputTypeGlobal = inputType
|
||||
switch (inputType) {
|
||||
case 0:
|
||||
information.innerText = "Welcome to the Burgernotes Migration wizard! Before we begin migration, there are a few things you should know."
|
||||
titleBox.innerText = "Burgernotes Migrator"
|
||||
backButton.classList.add("hidden")
|
||||
break
|
||||
case 1:
|
||||
information.innerText = "This migration process may corrupt your data if interrupted. Please ensure you have a stable internet connection. Press continue to download a backup of your notes, just in case."
|
||||
titleBox.innerText = "Disclaimer"
|
||||
backButton.classList.remove("hidden")
|
||||
break
|
||||
case 2:
|
||||
information.innerText = "Now that you have a backup of your notes, you can proceed to the next step. Press continue to begin migration."
|
||||
titleBox.innerText = "Backup Complete"
|
||||
break
|
||||
case 3:
|
||||
information.innerText = "You have successfully migrated to new Burgernotes! Enjoy the more secure and feature-rich experience. Click continue to return to the app."
|
||||
titleBox.innerText = "Migration Complete"
|
||||
fileInput.classList.remove("hidden")
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
function buttonClick() {
|
||||
switch (inputTypeGlobal) {
|
||||
case 0:
|
||||
showInput(1)
|
||||
break
|
||||
case 1:
|
||||
exportNotes().then((data) => {
|
||||
if (data !== undefined) {
|
||||
notesPlainText = data
|
||||
let blob = new Blob([JSON.stringify(data)], {type: "application/json"})
|
||||
let url = URL.createObjectURL(blob)
|
||||
let a = document.createElement("a")
|
||||
a.href = url
|
||||
a.download = "backup.json"
|
||||
a.click()
|
||||
showInput(2)
|
||||
}
|
||||
})
|
||||
break
|
||||
case 2:
|
||||
importNotes(notesPlainText).then((status) => {
|
||||
if (status === 200) {
|
||||
showInput(3)
|
||||
} else if (status === -1 || status === -2) {
|
||||
titleBox.innerText = "Error!"
|
||||
information.innerText = "An error occurred while migrating your notes. Please try again by pressing continue."
|
||||
console.log(status)
|
||||
} else {
|
||||
titleBox.innerText = "Critical Error!"
|
||||
information.innerText = "All your notes have been lost. Good thing you have a backup! Press continue to return to the app, so you can import your backup."
|
||||
inputTypeGlobal = 3
|
||||
}
|
||||
})
|
||||
break
|
||||
case 3:
|
||||
window.location.href = "/app"
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
function back() {
|
||||
showInput(inputTypeGlobal - 1)
|
||||
}
|
||||
|
||||
async function getKey() {
|
||||
let password = localStorage.getItem("DONOTSHARE-password")
|
||||
let salt = new TextEncoder().encode("I love Burgernotes!")
|
||||
let cryptoKey = await window.crypto.subtle.importKey("raw", new TextEncoder().encode(password), "PBKDF2", false, ["deriveBits", "deriveKey"])
|
||||
return await window.crypto.subtle.deriveKey({
|
||||
name: "PBKDF2",
|
||||
salt,
|
||||
iterations: 1,
|
||||
hash: "SHA-512"
|
||||
}, cryptoKey, {name: "AES-GCM", length: 256}, true, ["encrypt", "decrypt"])
|
||||
}
|
||||
|
||||
function arrayBufferToBase64(buffer) {
|
||||
const uint8Array = new Uint8Array(buffer);
|
||||
return btoa(String.fromCharCode.apply(null, uint8Array))
|
||||
}
|
||||
|
||||
async function encrypt(text) {
|
||||
let cryptoKey = await getKey()
|
||||
let iv = window.crypto.getRandomValues(new Uint8Array(12))
|
||||
let encrypted = await window.crypto.subtle.encrypt({
|
||||
name: "AES-GCM",
|
||||
iv: iv
|
||||
}, cryptoKey, new TextEncoder().encode(text))
|
||||
return btoa(JSON.stringify({
|
||||
encrypted: arrayBufferToBase64(encrypted),
|
||||
iv: arrayBufferToBase64(iv)
|
||||
}))
|
||||
}
|
||||
|
||||
async function importNotes(plaintextNotes) {
|
||||
try {
|
||||
for (let i in plaintextNotes) {
|
||||
plaintextNotes[i]["title"] = await encrypt(plaintextNotes[i]["title"])
|
||||
plaintextNotes[i]["content"] = await encrypt(plaintextNotes[i]["content"])
|
||||
}
|
||||
let purgeNotesFetch = await fetch(remote + "/api/purgenotes", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
"secretKey": localStorage.getItem("DONOTSHARE-secretkey"),
|
||||
}),
|
||||
headers: {
|
||||
"Content-Type": "application/json; charset=UTF-8"
|
||||
}
|
||||
})
|
||||
if (purgeNotesFetch.status !== 200) {
|
||||
return -2
|
||||
}
|
||||
let importNotesFetch = await fetch(remote + "/api/importnotes", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
"secretKey": localStorage.getItem("DONOTSHARE-secretkey"),
|
||||
"notes": JSON.stringify(plaintextNotes)
|
||||
}),
|
||||
headers: {
|
||||
"Content-Type": "application/json; charset=UTF-8"
|
||||
}
|
||||
})
|
||||
return importNotesFetch.status
|
||||
} catch (e) {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
async function exportNotes() {
|
||||
try {
|
||||
titleBox.innerText = "Decrypting Notes..."
|
||||
let oldPasswordFormat = await hashwasm.sha512(prompt("Please enter your password to decrypt your notes."))
|
||||
let exportNotesFetch = await fetch(remote + "/api/exportnotes", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
secretKey: localStorage.getItem("DONOTSHARE-secretkey")
|
||||
}),
|
||||
headers: {
|
||||
"Content-Type": "application/json; charset=UTF-8"
|
||||
}
|
||||
})
|
||||
let responseData = await exportNotesFetch.json()
|
||||
for (let i in responseData) {
|
||||
information.innerText = "Decrypting " + i + "/" + responseData.length
|
||||
|
||||
responseData[i]["title"] = CryptoJS.AES.decrypt(responseData[i]["title"], oldPasswordFormat).toString(CryptoJS.enc.Utf8)
|
||||
responseData[i]["content"] = CryptoJS.AES.decrypt(responseData[i]["content"], oldPasswordFormat).toString(CryptoJS.enc.Utf8)
|
||||
}
|
||||
titleBox.innerText = "Notes decrypted!"
|
||||
information.innerText = "Now if anything goes wrong, you can import this backup to restore your notes in the settings panel."
|
||||
return responseData
|
||||
} catch (e) {
|
||||
titleBox.innerText = "Error!"
|
||||
information.innerText = "An error occurred while decrypting your notes. Good thing we found out before we started migration! Please try again by pressing continue."
|
||||
}
|
||||
}
|
||||
|
||||
// @license-end
|
|
@ -18,24 +18,18 @@ let statusBox = document.getElementById("statusBox")
|
|||
let signupButton = document.getElementById("signupButton")
|
||||
let opButton = document.getElementById("opButton")
|
||||
|
||||
async function hashpassold(pass) {
|
||||
return await hashwasm.argon2id({
|
||||
password: pass,
|
||||
salt: await hashwasm.sha512(pass),
|
||||
parallelism: 1,
|
||||
iterations: 256,
|
||||
memorySize: 512,
|
||||
hashLength: 32,
|
||||
outputType: "encoded"
|
||||
})
|
||||
}
|
||||
// Leave these variables alone, they are used in the WASM code.
|
||||
|
||||
async function hashpass(pass) {
|
||||
let key = pass
|
||||
for (let i = 0; i < 128; i++) {
|
||||
key = await hashwasm.sha3(key)
|
||||
}
|
||||
return key
|
||||
return await hashwasm.argon2id({
|
||||
password: pass,
|
||||
salt: new TextEncoder().encode("I munch Burgers!!"),
|
||||
parallelism: 1,
|
||||
iterations: 32,
|
||||
memorySize: 19264,
|
||||
hashLength: 32,
|
||||
outputType: "hex"
|
||||
})
|
||||
}
|
||||
|
||||
function showElements(yesorno) {
|
||||
|
@ -44,12 +38,14 @@ function showElements(yesorno) {
|
|||
passwordBox.classList.add("hidden")
|
||||
signupButton.classList.add("hidden")
|
||||
opButton.classList.add("hidden")
|
||||
inputContainer.classList.add("hidden")
|
||||
}
|
||||
else {
|
||||
usernameBox.classList.remove("hidden")
|
||||
passwordBox.classList.remove("hidden")
|
||||
signupButton.classList.remove("hidden")
|
||||
opButton.classList.remove("hidden")
|
||||
inputContainer.classList.remove("hidden")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -61,53 +57,98 @@ opButton.addEventListener("click", () => {
|
|||
window.location.href = "/login"
|
||||
});
|
||||
|
||||
complete = new Event("completed");
|
||||
window.returnCode = undefined;
|
||||
window.returnVar = undefined;
|
||||
|
||||
// This is for the WASM code to call when it's done. Do not remove it, even if it looks like it's never called.
|
||||
|
||||
function WASMComplete() {
|
||||
window.dispatchEvent(complete);
|
||||
}
|
||||
|
||||
signupButton.addEventListener("click", () => {
|
||||
async function doStuff() {
|
||||
let username = usernameBox.value
|
||||
let password = passwordBox.value
|
||||
|
||||
if (username === "") {
|
||||
statusBox.innerText = "Username required ⚠️"
|
||||
statusBox.innerText = "Username required!"
|
||||
return
|
||||
}
|
||||
if ((username).length > 20) {
|
||||
statusBox.innerText = "Username cannot be more than 20 characters ⚠️"
|
||||
statusBox.innerText = "Username cannot be more than 20 characters!"
|
||||
return
|
||||
}
|
||||
if (password === "") {
|
||||
statusBox.innerText = "Password required ⚠️"
|
||||
statusBox.innerText = "Password required!"
|
||||
return
|
||||
}
|
||||
if ((password).length < 8) {
|
||||
statusBox.innerText = "Password must be at least 8 characters ⚠️"
|
||||
statusBox.innerText = "Password must be at least 8 characters!"
|
||||
return
|
||||
}
|
||||
|
||||
showElements(false)
|
||||
statusBox.innerText = "Creating account, please hold on..."
|
||||
statusBox.innerText = "Beginning proof-of-work challenge..."
|
||||
|
||||
/*
|
||||
* Compiled version of:
|
||||
* hashcat-wasm (https://concord.hectabit.org/hectabit/hashcat-wasm)
|
||||
* (c) Arzumify
|
||||
* @license AGPL-3.0
|
||||
* Since this is my software, if you use it with proprietary servers, I will make sure you will walk across hot coals (just kidding, probably).
|
||||
* I'm not kidding about the license though.
|
||||
* I should stop including comments into JS and possibly minify this code. Oh, well.
|
||||
*/
|
||||
|
||||
const go = new Go();
|
||||
WebAssembly.instantiateStreaming(fetch("/static/wasm/hashcat.wasm"), go.importObject).then((result) => {
|
||||
go.run(result.instance);
|
||||
})
|
||||
|
||||
window.addEventListener("completed", async () => {
|
||||
if (window.returnCode === 1) {
|
||||
statusBox.innerText = "Please do not expose your computer to cosmic rays (an impossible logical event has occurred)."
|
||||
showElements(true)
|
||||
return
|
||||
} else if (window.returnCode === 2) {
|
||||
statusBox.innerText = "The PoW Challenge has failed. Please try again."
|
||||
showElements(true)
|
||||
return
|
||||
}
|
||||
|
||||
statusBox.innerText = "Hashing password..."
|
||||
let hashedPassword = await hashpass(password)
|
||||
statusBox.innerText = "Contacting server..."
|
||||
fetch(remote + "/api/signup", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
username: username,
|
||||
password: await hashpass(password),
|
||||
legacyPassword: await hashpass(await hashpassold(password))
|
||||
password: hashedPassword,
|
||||
stamp: window.returnVar,
|
||||
}),
|
||||
headers: {
|
||||
"Content-Type": "application/json; charset=UTF-8",
|
||||
"X-Burgernotes-Version": "200"
|
||||
}
|
||||
})
|
||||
.then((response) => response)
|
||||
.then((response) => {
|
||||
async function doStuff() {
|
||||
.then(async (response) => {
|
||||
let responseData = await response.json()
|
||||
|
||||
if (response.status === 200) {
|
||||
statusBox.innerText = "Redirecting...."
|
||||
statusBox.innerText = "Setting up encryption keys..."
|
||||
localStorage.setItem("DONOTSHARE-secretkey", responseData["key"])
|
||||
localStorage.setItem("DONOTSHARE-password", await hashwasm.sha512(password))
|
||||
|
||||
localStorage.setItem("DONOTSHARE-password", await hashwasm.argon2id({
|
||||
password: password,
|
||||
salt: new TextEncoder().encode("I love Burgernotes!"),
|
||||
parallelism: 1,
|
||||
iterations: 32,
|
||||
memorySize: 19264,
|
||||
hashLength: 32,
|
||||
outputType: "hex"
|
||||
}))
|
||||
statusBox.innerText = "Welcome!"
|
||||
await new Promise(r => setTimeout(r, 200))
|
||||
window.location.href = "/app/"
|
||||
} else if (response.status === 409) {
|
||||
statusBox.innerText = "Username already taken!"
|
||||
|
@ -115,15 +156,15 @@ signupButton.addEventListener("click", () => {
|
|||
} else if (response.status === 429) {
|
||||
statusBox.innerText = "Please don't sign up to new accounts that quickly. If you are using a VPN, please turn it off!"
|
||||
showElements(true)
|
||||
} else if (response.status === 500) {
|
||||
statusBox.innerText = responseData["error"]
|
||||
showElements(true)
|
||||
} else {
|
||||
statusBox.innerText = "Something went wrong! (error code: " + response.status + ")"
|
||||
statusBox.innerText = "Something went wrong! (error: " + responseData["error"] + ")"
|
||||
showElements(true)
|
||||
}
|
||||
}
|
||||
doStuff()
|
||||
});
|
||||
}
|
||||
doStuff()
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
// @license-end
|
||||
|
|
10046
static/js/sodium-sumo.js
10046
static/js/sodium-sumo.js
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,567 @@
|
|||
// @license magnet:?xt=urn:btih:c80d50af7d3db9be66a4d0a86db0286e4fd33292&dn=bsd-3-clause.txt BSD-3-Clause
|
||||
|
||||
/*
|
||||
* wasm_exec (https://github.com/golang/go)
|
||||
* (c) The Go Authors
|
||||
* @license BSD-3-Clause
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
(() => {
|
||||
const enosys = () => {
|
||||
const err = new Error("not implemented");
|
||||
err.code = "ENOSYS";
|
||||
return err;
|
||||
};
|
||||
|
||||
if (!globalThis.fs) {
|
||||
let outputBuf = "";
|
||||
globalThis.fs = {
|
||||
constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused
|
||||
writeSync(fd, buf) {
|
||||
outputBuf += decoder.decode(buf);
|
||||
const nl = outputBuf.lastIndexOf("\n");
|
||||
if (nl != -1) {
|
||||
console.log(outputBuf.substring(0, nl));
|
||||
outputBuf = outputBuf.substring(nl + 1);
|
||||
}
|
||||
return buf.length;
|
||||
},
|
||||
write(fd, buf, offset, length, position, callback) {
|
||||
if (offset !== 0 || length !== buf.length || position !== null) {
|
||||
callback(enosys());
|
||||
return;
|
||||
}
|
||||
const n = this.writeSync(fd, buf);
|
||||
callback(null, n);
|
||||
},
|
||||
chmod(path, mode, callback) { callback(enosys()); },
|
||||
chown(path, uid, gid, callback) { callback(enosys()); },
|
||||
close(fd, callback) { callback(enosys()); },
|
||||
fchmod(fd, mode, callback) { callback(enosys()); },
|
||||
fchown(fd, uid, gid, callback) { callback(enosys()); },
|
||||
fstat(fd, callback) { callback(enosys()); },
|
||||
fsync(fd, callback) { callback(null); },
|
||||
ftruncate(fd, length, callback) { callback(enosys()); },
|
||||
lchown(path, uid, gid, callback) { callback(enosys()); },
|
||||
link(path, link, callback) { callback(enosys()); },
|
||||
lstat(path, callback) { callback(enosys()); },
|
||||
mkdir(path, perm, callback) { callback(enosys()); },
|
||||
open(path, flags, mode, callback) { callback(enosys()); },
|
||||
read(fd, buffer, offset, length, position, callback) { callback(enosys()); },
|
||||
readdir(path, callback) { callback(enosys()); },
|
||||
readlink(path, callback) { callback(enosys()); },
|
||||
rename(from, to, callback) { callback(enosys()); },
|
||||
rmdir(path, callback) { callback(enosys()); },
|
||||
stat(path, callback) { callback(enosys()); },
|
||||
symlink(path, link, callback) { callback(enosys()); },
|
||||
truncate(path, length, callback) { callback(enosys()); },
|
||||
unlink(path, callback) { callback(enosys()); },
|
||||
utimes(path, atime, mtime, callback) { callback(enosys()); },
|
||||
};
|
||||
}
|
||||
|
||||
if (!globalThis.process) {
|
||||
globalThis.process = {
|
||||
getuid() { return -1; },
|
||||
getgid() { return -1; },
|
||||
geteuid() { return -1; },
|
||||
getegid() { return -1; },
|
||||
getgroups() { throw enosys(); },
|
||||
pid: -1,
|
||||
ppid: -1,
|
||||
umask() { throw enosys(); },
|
||||
cwd() { throw enosys(); },
|
||||
chdir() { throw enosys(); },
|
||||
}
|
||||
}
|
||||
|
||||
if (!globalThis.crypto) {
|
||||
throw new Error("globalThis.crypto is not available, polyfill required (crypto.getRandomValues only)");
|
||||
}
|
||||
|
||||
if (!globalThis.performance) {
|
||||
throw new Error("globalThis.performance is not available, polyfill required (performance.now only)");
|
||||
}
|
||||
|
||||
if (!globalThis.TextEncoder) {
|
||||
throw new Error("globalThis.TextEncoder is not available, polyfill required");
|
||||
}
|
||||
|
||||
if (!globalThis.TextDecoder) {
|
||||
throw new Error("globalThis.TextDecoder is not available, polyfill required");
|
||||
}
|
||||
|
||||
const encoder = new TextEncoder("utf-8");
|
||||
const decoder = new TextDecoder("utf-8");
|
||||
|
||||
globalThis.Go = class {
|
||||
constructor() {
|
||||
this.argv = ["js"];
|
||||
this.env = {};
|
||||
this.exit = (code) => {
|
||||
if (code !== 0) {
|
||||
console.warn("exit code:", code);
|
||||
}
|
||||
};
|
||||
this._exitPromise = new Promise((resolve) => {
|
||||
this._resolveExitPromise = resolve;
|
||||
});
|
||||
this._pendingEvent = null;
|
||||
this._scheduledTimeouts = new Map();
|
||||
this._nextCallbackTimeoutID = 1;
|
||||
|
||||
const setInt64 = (addr, v) => {
|
||||
this.mem.setUint32(addr + 0, v, true);
|
||||
this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true);
|
||||
}
|
||||
|
||||
const setInt32 = (addr, v) => {
|
||||
this.mem.setUint32(addr + 0, v, true);
|
||||
}
|
||||
|
||||
const getInt64 = (addr) => {
|
||||
const low = this.mem.getUint32(addr + 0, true);
|
||||
const high = this.mem.getInt32(addr + 4, true);
|
||||
return low + high * 4294967296;
|
||||
}
|
||||
|
||||
const loadValue = (addr) => {
|
||||
const f = this.mem.getFloat64(addr, true);
|
||||
if (f === 0) {
|
||||
return undefined;
|
||||
}
|
||||
if (!isNaN(f)) {
|
||||
return f;
|
||||
}
|
||||
|
||||
const id = this.mem.getUint32(addr, true);
|
||||
return this._values[id];
|
||||
}
|
||||
|
||||
const storeValue = (addr, v) => {
|
||||
const nanHead = 0x7FF80000;
|
||||
|
||||
if (typeof v === "number" && v !== 0) {
|
||||
if (isNaN(v)) {
|
||||
this.mem.setUint32(addr + 4, nanHead, true);
|
||||
this.mem.setUint32(addr, 0, true);
|
||||
return;
|
||||
}
|
||||
this.mem.setFloat64(addr, v, true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (v === undefined) {
|
||||
this.mem.setFloat64(addr, 0, true);
|
||||
return;
|
||||
}
|
||||
|
||||
let id = this._ids.get(v);
|
||||
if (id === undefined) {
|
||||
id = this._idPool.pop();
|
||||
if (id === undefined) {
|
||||
id = this._values.length;
|
||||
}
|
||||
this._values[id] = v;
|
||||
this._goRefCounts[id] = 0;
|
||||
this._ids.set(v, id);
|
||||
}
|
||||
this._goRefCounts[id]++;
|
||||
let typeFlag = 0;
|
||||
switch (typeof v) {
|
||||
case "object":
|
||||
if (v !== null) {
|
||||
typeFlag = 1;
|
||||
}
|
||||
break;
|
||||
case "string":
|
||||
typeFlag = 2;
|
||||
break;
|
||||
case "symbol":
|
||||
typeFlag = 3;
|
||||
break;
|
||||
case "function":
|
||||
typeFlag = 4;
|
||||
break;
|
||||
}
|
||||
this.mem.setUint32(addr + 4, nanHead | typeFlag, true);
|
||||
this.mem.setUint32(addr, id, true);
|
||||
}
|
||||
|
||||
const loadSlice = (addr) => {
|
||||
const array = getInt64(addr + 0);
|
||||
const len = getInt64(addr + 8);
|
||||
return new Uint8Array(this._inst.exports.mem.buffer, array, len);
|
||||
}
|
||||
|
||||
const loadSliceOfValues = (addr) => {
|
||||
const array = getInt64(addr + 0);
|
||||
const len = getInt64(addr + 8);
|
||||
const a = new Array(len);
|
||||
for (let i = 0; i < len; i++) {
|
||||
a[i] = loadValue(array + i * 8);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
const loadString = (addr) => {
|
||||
const saddr = getInt64(addr + 0);
|
||||
const len = getInt64(addr + 8);
|
||||
return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len));
|
||||
}
|
||||
|
||||
const timeOrigin = Date.now() - performance.now();
|
||||
this.importObject = {
|
||||
_gotest: {
|
||||
add: (a, b) => a + b,
|
||||
},
|
||||
gojs: {
|
||||
// Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters)
|
||||
// may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported
|
||||
// function. A goroutine can switch to a new stack if the current stack is too small (see morestack function).
|
||||
// This changes the SP, thus we have to update the SP used by the imported function.
|
||||
|
||||
// func wasmExit(code int32)
|
||||
"runtime.wasmExit": (sp) => {
|
||||
sp >>>= 0;
|
||||
const code = this.mem.getInt32(sp + 8, true);
|
||||
this.exited = true;
|
||||
delete this._inst;
|
||||
delete this._values;
|
||||
delete this._goRefCounts;
|
||||
delete this._ids;
|
||||
delete this._idPool;
|
||||
this.exit(code);
|
||||
},
|
||||
|
||||
// func wasmWrite(fd uintptr, p unsafe.Pointer, n int32)
|
||||
"runtime.wasmWrite": (sp) => {
|
||||
sp >>>= 0;
|
||||
const fd = getInt64(sp + 8);
|
||||
const p = getInt64(sp + 16);
|
||||
const n = this.mem.getInt32(sp + 24, true);
|
||||
fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n));
|
||||
},
|
||||
|
||||
// func resetMemoryDataView()
|
||||
"runtime.resetMemoryDataView": (sp) => {
|
||||
sp >>>= 0;
|
||||
this.mem = new DataView(this._inst.exports.mem.buffer);
|
||||
},
|
||||
|
||||
// func nanotime1() int64
|
||||
"runtime.nanotime1": (sp) => {
|
||||
sp >>>= 0;
|
||||
setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000);
|
||||
},
|
||||
|
||||
// func walltime() (sec int64, nsec int32)
|
||||
"runtime.walltime": (sp) => {
|
||||
sp >>>= 0;
|
||||
const msec = (new Date).getTime();
|
||||
setInt64(sp + 8, msec / 1000);
|
||||
this.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true);
|
||||
},
|
||||
|
||||
// func scheduleTimeoutEvent(delay int64) int32
|
||||
"runtime.scheduleTimeoutEvent": (sp) => {
|
||||
sp >>>= 0;
|
||||
const id = this._nextCallbackTimeoutID;
|
||||
this._nextCallbackTimeoutID++;
|
||||
this._scheduledTimeouts.set(id, setTimeout(
|
||||
() => {
|
||||
this._resume();
|
||||
while (this._scheduledTimeouts.has(id)) {
|
||||
// for some reason Go failed to register the timeout event, log and try again
|
||||
// (temporary workaround for https://github.com/golang/go/issues/28975)
|
||||
console.warn("scheduleTimeoutEvent: missed timeout event");
|
||||
this._resume();
|
||||
}
|
||||
},
|
||||
getInt64(sp + 8),
|
||||
));
|
||||
this.mem.setInt32(sp + 16, id, true);
|
||||
},
|
||||
|
||||
// func clearTimeoutEvent(id int32)
|
||||
"runtime.clearTimeoutEvent": (sp) => {
|
||||
sp >>>= 0;
|
||||
const id = this.mem.getInt32(sp + 8, true);
|
||||
clearTimeout(this._scheduledTimeouts.get(id));
|
||||
this._scheduledTimeouts.delete(id);
|
||||
},
|
||||
|
||||
// func getRandomData(r []byte)
|
||||
"runtime.getRandomData": (sp) => {
|
||||
sp >>>= 0;
|
||||
crypto.getRandomValues(loadSlice(sp + 8));
|
||||
},
|
||||
|
||||
// func finalizeRef(v ref)
|
||||
"syscall/js.finalizeRef": (sp) => {
|
||||
sp >>>= 0;
|
||||
const id = this.mem.getUint32(sp + 8, true);
|
||||
this._goRefCounts[id]--;
|
||||
if (this._goRefCounts[id] === 0) {
|
||||
const v = this._values[id];
|
||||
this._values[id] = null;
|
||||
this._ids.delete(v);
|
||||
this._idPool.push(id);
|
||||
}
|
||||
},
|
||||
|
||||
// func stringVal(value string) ref
|
||||
"syscall/js.stringVal": (sp) => {
|
||||
sp >>>= 0;
|
||||
storeValue(sp + 24, loadString(sp + 8));
|
||||
},
|
||||
|
||||
// func valueGet(v ref, p string) ref
|
||||
"syscall/js.valueGet": (sp) => {
|
||||
sp >>>= 0;
|
||||
const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16));
|
||||
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
||||
storeValue(sp + 32, result);
|
||||
},
|
||||
|
||||
// func valueSet(v ref, p string, x ref)
|
||||
"syscall/js.valueSet": (sp) => {
|
||||
sp >>>= 0;
|
||||
Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32));
|
||||
},
|
||||
|
||||
// func valueDelete(v ref, p string)
|
||||
"syscall/js.valueDelete": (sp) => {
|
||||
sp >>>= 0;
|
||||
Reflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16));
|
||||
},
|
||||
|
||||
// func valueIndex(v ref, i int) ref
|
||||
"syscall/js.valueIndex": (sp) => {
|
||||
sp >>>= 0;
|
||||
storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16)));
|
||||
},
|
||||
|
||||
// valueSetIndex(v ref, i int, x ref)
|
||||
"syscall/js.valueSetIndex": (sp) => {
|
||||
sp >>>= 0;
|
||||
Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24));
|
||||
},
|
||||
|
||||
// func valueCall(v ref, m string, args []ref) (ref, bool)
|
||||
"syscall/js.valueCall": (sp) => {
|
||||
sp >>>= 0;
|
||||
try {
|
||||
const v = loadValue(sp + 8);
|
||||
const m = Reflect.get(v, loadString(sp + 16));
|
||||
const args = loadSliceOfValues(sp + 32);
|
||||
const result = Reflect.apply(m, v, args);
|
||||
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
||||
storeValue(sp + 56, result);
|
||||
this.mem.setUint8(sp + 64, 1);
|
||||
} catch (err) {
|
||||
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
||||
storeValue(sp + 56, err);
|
||||
this.mem.setUint8(sp + 64, 0);
|
||||
}
|
||||
},
|
||||
|
||||
// func valueInvoke(v ref, args []ref) (ref, bool)
|
||||
"syscall/js.valueInvoke": (sp) => {
|
||||
sp >>>= 0;
|
||||
try {
|
||||
const v = loadValue(sp + 8);
|
||||
const args = loadSliceOfValues(sp + 16);
|
||||
const result = Reflect.apply(v, undefined, args);
|
||||
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
||||
storeValue(sp + 40, result);
|
||||
this.mem.setUint8(sp + 48, 1);
|
||||
} catch (err) {
|
||||
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
||||
storeValue(sp + 40, err);
|
||||
this.mem.setUint8(sp + 48, 0);
|
||||
}
|
||||
},
|
||||
|
||||
// func valueNew(v ref, args []ref) (ref, bool)
|
||||
"syscall/js.valueNew": (sp) => {
|
||||
sp >>>= 0;
|
||||
try {
|
||||
const v = loadValue(sp + 8);
|
||||
const args = loadSliceOfValues(sp + 16);
|
||||
const result = Reflect.construct(v, args);
|
||||
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
||||
storeValue(sp + 40, result);
|
||||
this.mem.setUint8(sp + 48, 1);
|
||||
} catch (err) {
|
||||
sp = this._inst.exports.getsp() >>> 0; // see comment above
|
||||
storeValue(sp + 40, err);
|
||||
this.mem.setUint8(sp + 48, 0);
|
||||
}
|
||||
},
|
||||
|
||||
// func valueLength(v ref) int
|
||||
"syscall/js.valueLength": (sp) => {
|
||||
sp >>>= 0;
|
||||
setInt64(sp + 16, parseInt(loadValue(sp + 8).length));
|
||||
},
|
||||
|
||||
// valuePrepareString(v ref) (ref, int)
|
||||
"syscall/js.valuePrepareString": (sp) => {
|
||||
sp >>>= 0;
|
||||
const str = encoder.encode(String(loadValue(sp + 8)));
|
||||
storeValue(sp + 16, str);
|
||||
setInt64(sp + 24, str.length);
|
||||
},
|
||||
|
||||
// valueLoadString(v ref, b []byte)
|
||||
"syscall/js.valueLoadString": (sp) => {
|
||||
sp >>>= 0;
|
||||
const str = loadValue(sp + 8);
|
||||
loadSlice(sp + 16).set(str);
|
||||
},
|
||||
|
||||
// func valueInstanceOf(v ref, t ref) bool
|
||||
"syscall/js.valueInstanceOf": (sp) => {
|
||||
sp >>>= 0;
|
||||
this.mem.setUint8(sp + 24, (loadValue(sp + 8) instanceof loadValue(sp + 16)) ? 1 : 0);
|
||||
},
|
||||
|
||||
// func copyBytesToGo(dst []byte, src ref) (int, bool)
|
||||
"syscall/js.copyBytesToGo": (sp) => {
|
||||
sp >>>= 0;
|
||||
const dst = loadSlice(sp + 8);
|
||||
const src = loadValue(sp + 32);
|
||||
if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) {
|
||||
this.mem.setUint8(sp + 48, 0);
|
||||
return;
|
||||
}
|
||||
const toCopy = src.subarray(0, dst.length);
|
||||
dst.set(toCopy);
|
||||
setInt64(sp + 40, toCopy.length);
|
||||
this.mem.setUint8(sp + 48, 1);
|
||||
},
|
||||
|
||||
// func copyBytesToJS(dst ref, src []byte) (int, bool)
|
||||
"syscall/js.copyBytesToJS": (sp) => {
|
||||
sp >>>= 0;
|
||||
const dst = loadValue(sp + 8);
|
||||
const src = loadSlice(sp + 16);
|
||||
if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) {
|
||||
this.mem.setUint8(sp + 48, 0);
|
||||
return;
|
||||
}
|
||||
const toCopy = src.subarray(0, dst.length);
|
||||
dst.set(toCopy);
|
||||
setInt64(sp + 40, toCopy.length);
|
||||
this.mem.setUint8(sp + 48, 1);
|
||||
},
|
||||
|
||||
"debug": (value) => {
|
||||
console.log(value);
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async run(instance) {
|
||||
if (!(instance instanceof WebAssembly.Instance)) {
|
||||
throw new Error("Go.run: WebAssembly.Instance expected");
|
||||
}
|
||||
this._inst = instance;
|
||||
this.mem = new DataView(this._inst.exports.mem.buffer);
|
||||
this._values = [ // JS values that Go currently has references to, indexed by reference id
|
||||
NaN,
|
||||
0,
|
||||
null,
|
||||
true,
|
||||
false,
|
||||
globalThis,
|
||||
this,
|
||||
];
|
||||
this._goRefCounts = new Array(this._values.length).fill(Infinity); // number of references that Go has to a JS value, indexed by reference id
|
||||
this._ids = new Map([ // mapping from JS values to reference ids
|
||||
[0, 1],
|
||||
[null, 2],
|
||||
[true, 3],
|
||||
[false, 4],
|
||||
[globalThis, 5],
|
||||
[this, 6],
|
||||
]);
|
||||
this._idPool = []; // unused ids that have been garbage collected
|
||||
this.exited = false; // whether the Go program has exited
|
||||
|
||||
// Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory.
|
||||
let offset = 4096;
|
||||
|
||||
const strPtr = (str) => {
|
||||
const ptr = offset;
|
||||
const bytes = encoder.encode(str + "\0");
|
||||
new Uint8Array(this.mem.buffer, offset, bytes.length).set(bytes);
|
||||
offset += bytes.length;
|
||||
if (offset % 8 !== 0) {
|
||||
offset += 8 - (offset % 8);
|
||||
}
|
||||
return ptr;
|
||||
};
|
||||
|
||||
const argc = this.argv.length;
|
||||
|
||||
const argvPtrs = [];
|
||||
this.argv.forEach((arg) => {
|
||||
argvPtrs.push(strPtr(arg));
|
||||
});
|
||||
argvPtrs.push(0);
|
||||
|
||||
const keys = Object.keys(this.env).sort();
|
||||
keys.forEach((key) => {
|
||||
argvPtrs.push(strPtr(`${key}=${this.env[key]}`));
|
||||
});
|
||||
argvPtrs.push(0);
|
||||
|
||||
const argv = offset;
|
||||
argvPtrs.forEach((ptr) => {
|
||||
this.mem.setUint32(offset, ptr, true);
|
||||
this.mem.setUint32(offset + 4, 0, true);
|
||||
offset += 8;
|
||||
});
|
||||
|
||||
// The linker guarantees global data starts from at least wasmMinDataAddr.
|
||||
// Keep in sync with cmd/link/internal/ld/data.go:wasmMinDataAddr.
|
||||
const wasmMinDataAddr = 4096 + 8192;
|
||||
if (offset >= wasmMinDataAddr) {
|
||||
throw new Error("total length of command line and environment variables exceeds limit");
|
||||
}
|
||||
|
||||
this._inst.exports.run(argc, argv);
|
||||
if (this.exited) {
|
||||
this._resolveExitPromise();
|
||||
}
|
||||
await this._exitPromise;
|
||||
}
|
||||
|
||||
_resume() {
|
||||
if (this.exited) {
|
||||
throw new Error("Go program has already exited");
|
||||
}
|
||||
this._inst.exports.resume();
|
||||
if (this.exited) {
|
||||
this._resolveExitPromise();
|
||||
}
|
||||
}
|
||||
|
||||
_makeFuncWrapper(id) {
|
||||
const go = this;
|
||||
return function () {
|
||||
const event = { id: id, this: this, args: arguments };
|
||||
go._pendingEvent = event;
|
||||
go._resume();
|
||||
return event.result;
|
||||
};
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
// @license-end
|
Reference in New Issue