forked from AbleOS/holey-bytes
some progress
This commit is contained in:
parent
13f63c7700
commit
1626734c1a
|
@ -4,8 +4,12 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
aes-gcm = { version = "0.10.3", default-features = false, features = ["aes", "rand_core"] }
|
anyhow = "1.0.89"
|
||||||
ed25519-dalek = { version = "2.1.1", default-features = false, features = ["rand_core"] }
|
axum = "0.7.7"
|
||||||
getrandom = "0.2.15"
|
getrandom = "0.2.15"
|
||||||
rand_core = { version = "0.6.4", features = ["getrandom"] }
|
htmlm = "0.3.0"
|
||||||
x25519-dalek = { version = "2.0.1", default-features = false }
|
log = "0.4.22"
|
||||||
|
rusqlite = "0.32.1"
|
||||||
|
serde = { version = "1.0.210", features = ["derive"] }
|
||||||
|
time = "0.3.36"
|
||||||
|
tokio = { version = "1.40.0", features = ["rt"] }
|
||||||
|
|
88
depell/src/index.css
Normal file
88
depell/src/index.css
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
* {
|
||||||
|
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
--primary: white;
|
||||||
|
--secondary: #EFEFEF;
|
||||||
|
--error: #ff3333;
|
||||||
|
--placeholder: #333333;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
--small-gap: 5px;
|
||||||
|
--monospace: 'Courier New', Courier, monospace;
|
||||||
|
|
||||||
|
nav {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
section:last-child {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--small-gap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
margin-top: var(--small-gap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
div.preview {
|
||||||
|
div.stats {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--small-gap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--small-gap);
|
||||||
|
|
||||||
|
::placeholder {
|
||||||
|
color: var(--placeholder);
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: var(--error);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pre,
|
||||||
|
textarea {
|
||||||
|
outline: none;
|
||||||
|
border: none;
|
||||||
|
background: var(--secondary);
|
||||||
|
padding: var(--small-gap);
|
||||||
|
padding-top: calc(var(--small-gap) * 1.5);
|
||||||
|
margin: var(--small-gap) 0px;
|
||||||
|
font-family: var(--monospace);
|
||||||
|
resize: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
font-size: inherit;
|
||||||
|
outline: none;
|
||||||
|
border: none;
|
||||||
|
background: var(--secondary);
|
||||||
|
padding: var(--small-gap);
|
||||||
|
}
|
||||||
|
|
||||||
|
input:is(:hover, :focus) {
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
font-size: inherit;
|
||||||
|
background: var(--secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover:not(:active) {
|
||||||
|
background: white;
|
||||||
|
}
|
95
depell/src/index.js
Normal file
95
depell/src/index.js
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
//// @ts-check
|
||||||
|
|
||||||
|
if (window.location.hostname === 'localhost') {
|
||||||
|
let id; setInterval(async () => {
|
||||||
|
let new_id = await fetch('/hot-reload').then(reps => reps.text());
|
||||||
|
id ??= new_id;
|
||||||
|
if (id !== new_id) window.location.reload();
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.body.addEventListener('htmx:afterSwap', (ev) => {
|
||||||
|
wireUp(ev.target);
|
||||||
|
});
|
||||||
|
|
||||||
|
wireUp(document.body);
|
||||||
|
|
||||||
|
/** @param {HTMLElement} target */
|
||||||
|
function wireUp(target) {
|
||||||
|
execApply(target);
|
||||||
|
cacheInputs(target);
|
||||||
|
bindTextareaAutoResize(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {string} content @return {string} */
|
||||||
|
function fmtTimestamp(content) {
|
||||||
|
new Date(parseInt(content) * 1000).toLocaleString()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {HTMLElement} target */
|
||||||
|
function execApply(target) {
|
||||||
|
/**@type {HTMLElement}*/ let elem;
|
||||||
|
for (elem of target.querySelectorAll('[apply]')) {
|
||||||
|
const funcname = elem.getAttribute('apply');
|
||||||
|
elem.textContent = window[funcname](elem.textContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {HTMLElement} target */
|
||||||
|
function bindTextareaAutoResize(target) {
|
||||||
|
/**@type {HTMLTextAreaElement}*/ let textarea;
|
||||||
|
for (textarea of target.querySelectorAll("textarea")) {
|
||||||
|
textarea.style.height = textarea.scrollHeight + "px";
|
||||||
|
textarea.style.overflowY = "hidden";
|
||||||
|
textarea.addEventListener("input", function() {
|
||||||
|
textarea.style.height = "auto";
|
||||||
|
textarea.style.height = textarea.scrollHeight + "px";
|
||||||
|
});
|
||||||
|
|
||||||
|
textarea.onkeydown = (ev) => {
|
||||||
|
const selecting = textarea.selectionStart !== textarea.selectionEnd;
|
||||||
|
|
||||||
|
if (ev.key === "Tab") {
|
||||||
|
ev.preventDefault();
|
||||||
|
const prevPos = textarea.selectionStart;
|
||||||
|
textarea.value = textarea.value.slice(0, textarea.selectionStart) +
|
||||||
|
' ' + textarea.value.slice(textarea.selectionEnd);
|
||||||
|
textarea.selectionStart = textarea.selectionEnd = prevPos + 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ev.key === "Backspace" && textarea.selectionStart != 0 && !selecting) {
|
||||||
|
let i = textarea.selectionStart, looped = false;
|
||||||
|
while (textarea.value.charCodeAt(--i) === ' '.charCodeAt(0)) looped = true;
|
||||||
|
if (textarea.value.charCodeAt(i) === '\n'.charCodeAt(0) && looped) {
|
||||||
|
ev.preventDefault();
|
||||||
|
let toDelete = (textarea.selectionStart - (i + 1)) % 4;
|
||||||
|
if (toDelete === 0) toDelete = 4;
|
||||||
|
const prevPos = textarea.selectionStart;
|
||||||
|
textarea.value = textarea.value.slice(0, textarea.selectionStart - toDelete) +
|
||||||
|
textarea.value.slice(textarea.selectionEnd);
|
||||||
|
textarea.selectionStart = textarea.selectionEnd = prevPos - toDelete;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {HTMLElement} target */
|
||||||
|
function cacheInputs(target) {
|
||||||
|
/**@type {HTMLFormElement}*/ let form;
|
||||||
|
for (form of target.querySelectorAll('form')) {
|
||||||
|
const path = form.getAttribute('hx-post') || form.getAttribute('hx-delete');
|
||||||
|
if (!path) {
|
||||||
|
console.warn('form does not have a hx-post or hx-delete attribute', form);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**@type {HTMLInputElement}*/ let input;
|
||||||
|
for (input of form.elements) {
|
||||||
|
if ('password submit button'.includes(input.type)) continue;
|
||||||
|
const key = path + input.name;
|
||||||
|
input.value = localStorage.getItem(key) ?? '';
|
||||||
|
input.addEventListener("input", (ev) => localStorage.setItem(key, ev.target.value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1094
depell/src/main.rs
1094
depell/src/main.rs
File diff suppressed because it is too large
Load diff
12
depell/src/post-page.html
Normal file
12
depell/src/post-page.html
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<h3>About posting code</h3>
|
||||||
|
<p>
|
||||||
|
If you are unfammiliar with <a href="https://git.ablecorp.us/AbleOS/holey-bytes">hblang</a>, refer to the
|
||||||
|
<strong>hblang/README.md</strong> or
|
||||||
|
vizit <a href="/profile/mlokis">mlokis'es posts</a>. Preferably don't edit the code here.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>Extra textarea features</h3>
|
||||||
|
<ul>
|
||||||
|
<li>proper tab behaviour</li>
|
||||||
|
<li>snap to previous tab boundary on "empty" lines</li>
|
||||||
|
</ul>
|
0
depell/src/profile-page.html
Normal file
0
depell/src/profile-page.html
Normal file
51
depell/src/schema.sql
Normal file
51
depell/src/schema.sql
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
PRAGMA foreign_keys = ON;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS user(
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
password_hash TEXT NOT NULL,
|
||||||
|
PRIMARY KEY (name)
|
||||||
|
) WITHOUT ROWID;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS session(
|
||||||
|
id BLOB NOT NULL,
|
||||||
|
username TEXT NOT NULL,
|
||||||
|
expiration INTEGER NOT NULL,
|
||||||
|
FOREIGN KEY (username) REFERENCES user (name)
|
||||||
|
PRIMARY KEY (username)
|
||||||
|
) WITHOUT ROWID;
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS
|
||||||
|
session_id ON session (id);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS post(
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
author TEXT,
|
||||||
|
timestamp INTEGER,
|
||||||
|
code TEXT NOT NULL,
|
||||||
|
FOREIGN KEY (author) REFERENCES user (name) ON DELETE SET NULL,
|
||||||
|
PRIMARY KEY (author, name)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS import(
|
||||||
|
from_name TEXT NOT NULL,
|
||||||
|
from_author TEXT,
|
||||||
|
to_name TEXT NOT NULL,
|
||||||
|
to_author TEXT,
|
||||||
|
FOREIGN KEY (from_name, from_author) REFERENCES post (name, author),
|
||||||
|
FOREIGN KEY (to_name, to_author) REFERENCES post (name, author)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS
|
||||||
|
dependencies ON import(from_name, from_author);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS
|
||||||
|
dependants ON import(to_name, to_author);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS run(
|
||||||
|
code_name TEXT NOT NULL,
|
||||||
|
code_author TEXT NOT NULL,
|
||||||
|
runner TEXT NOT NULL,
|
||||||
|
FOREIGN KEY (code_name, code_author) REFERENCES post (name, author),
|
||||||
|
FOREIGN KEY (runner) REFERENCES user(name),
|
||||||
|
PRIMARY KEY (code_name, code_author, runner)
|
||||||
|
);
|
17
depell/src/welcome-page.html
Normal file
17
depell/src/welcome-page.html
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<h1>Welcome to depell</h1>
|
||||||
|
<p>
|
||||||
|
Depell (dependency hell) is a simple "social" media site best compared to twitter, except that all you can post is
|
||||||
|
<a href="https://git.ablecorp.us/AbleOS/holey-bytes">hblang</a> code with no comments allowed. Instead of likes you
|
||||||
|
run the program, and instead of retweets you import the program as dependency. Run counts even when ran indirectly.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The backend only serves the code and frontend compiles and runs it locally. All posts are immutable.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Security?</h2>
|
||||||
|
<p>
|
||||||
|
All code runs in WASM (inside a holey-bytes VM until hblang compiles to wasm) and is controlled by JavaScript. WASM
|
||||||
|
cant do any form of IO without going trough JavaScript so as long as JS import does not allow wasm to execute
|
||||||
|
arbitrary JS code, WASM can act as a container inside the JS.
|
||||||
|
</p>
|
11
depell/wasm-hbfmt/Cargo.toml
Normal file
11
depell/wasm-hbfmt/Cargo.toml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
[package]
|
||||||
|
name = "wasm-hbfmt"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
hblang = { version = "0.1.0", path = "../../hblang", default-features = false }
|
||||||
|
log = { version = "0.4.22", features = ["max_level_off"] }
|
101
depell/wasm-hbfmt/src/lib.rs
Normal file
101
depell/wasm-hbfmt/src/lib.rs
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
#![no_std]
|
||||||
|
#![feature(slice_take)]
|
||||||
|
#![feature(str_from_raw_parts)]
|
||||||
|
|
||||||
|
use hblang::parser::ParserCtx;
|
||||||
|
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
#[panic_handler]
|
||||||
|
fn handle_panic(_: &core::panic::PanicInfo) -> ! {
|
||||||
|
core::arch::wasm32::unreachable()
|
||||||
|
}
|
||||||
|
|
||||||
|
use core::{
|
||||||
|
alloc::{GlobalAlloc, Layout},
|
||||||
|
cell::UnsafeCell,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ARENA_SIZE: usize = 128 * 1024;
|
||||||
|
|
||||||
|
#[repr(C, align(32))]
|
||||||
|
struct SimpleAllocator {
|
||||||
|
arena: UnsafeCell<[u8; ARENA_SIZE]>,
|
||||||
|
head: UnsafeCell<*mut u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SimpleAllocator {
|
||||||
|
const fn new() -> Self {
|
||||||
|
SimpleAllocator {
|
||||||
|
arena: UnsafeCell::new([0; ARENA_SIZE]),
|
||||||
|
head: UnsafeCell::new(core::ptr::null_mut()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn reset(&self) {
|
||||||
|
(*self.head.get()) = self.arena.get().cast::<u8>().add(ARENA_SIZE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Sync for SimpleAllocator {}
|
||||||
|
|
||||||
|
unsafe impl GlobalAlloc for SimpleAllocator {
|
||||||
|
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
|
||||||
|
let size = layout.size();
|
||||||
|
let align = layout.align();
|
||||||
|
|
||||||
|
let until = self.arena.get() as *mut u8;
|
||||||
|
|
||||||
|
let new_head = (*self.head.get()).sub(size);
|
||||||
|
let aligned_head = (new_head as usize & (1 << (align - 1))) as *mut u8;
|
||||||
|
|
||||||
|
if until > aligned_head {
|
||||||
|
return core::ptr::null_mut();
|
||||||
|
}
|
||||||
|
|
||||||
|
*self.head.get() = aligned_head;
|
||||||
|
aligned_head
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) {
|
||||||
|
/* lol */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(target_arch = "wasm32", global_allocator)]
|
||||||
|
static ALLOCATOR: SimpleAllocator = SimpleAllocator::new();
|
||||||
|
|
||||||
|
const MAX_OUTPUT_SIZE: usize = 1024 * 10;
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
static mut OUTPUT: [u8; MAX_OUTPUT_SIZE] = unsafe { core::mem::zeroed() };
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
static mut OUTPUT_LEN: usize = 0;
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
unsafe extern "C" fn fmt(code: *const u8, len: usize) {
|
||||||
|
ALLOCATOR.reset();
|
||||||
|
|
||||||
|
let code = core::str::from_raw_parts(code, len);
|
||||||
|
|
||||||
|
let arena = hblang::parser::Arena::default();
|
||||||
|
let mut ctx = ParserCtx::default();
|
||||||
|
let exprs = hblang::parser::Parser::parse(&mut ctx, code, "source.hb", &|_, _| Ok(0), &arena);
|
||||||
|
|
||||||
|
struct Write<'a>(&'a mut [u8]);
|
||||||
|
|
||||||
|
impl core::fmt::Write for Write<'_> {
|
||||||
|
fn write_str(&mut self, s: &str) -> core::fmt::Result {
|
||||||
|
if let Some(m) = self.0.take_mut(..s.len()) {
|
||||||
|
m.copy_from_slice(s.as_bytes());
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(core::fmt::Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut f = Write(unsafe { &mut OUTPUT[..] });
|
||||||
|
hblang::fmt::fmt_file(exprs, code, &mut f).unwrap();
|
||||||
|
unsafe { OUTPUT_LEN = MAX_OUTPUT_SIZE - f.0.len() };
|
||||||
|
}
|
Loading…
Reference in a new issue