getting things going

This commit is contained in:
metacryst
2026-01-29 08:20:27 -06:00
parent c09b08a474
commit 6d50337e3b
9 changed files with 377 additions and 2 deletions

47
src/Login.js Normal file
View File

@@ -0,0 +1,47 @@
class Login extends Shadow {
inputStyles(el) {
return el
.background("var(--main)")
.color("var(--accent)")
.border("1px solid var(--accent)")
}
render() {
ZStack(() => {
img("/_/icons/column.svg", window.isMobile() ? "5vmax" : "3vmax")
.position("absolute")
.top(2, em)
.left(2, em)
.onClick((done) => {
window.navigateTo("/")
})
form(() => {
input("Email", "60vw")
.attr({name: "email", type: "email"})
.margin(1, em)
.padding(1, em)
.styles(this.inputStyles)
input("Password", "60vw")
.attr({name: "password", type: "password"})
.margin(1, em)
.padding(1, em)
.styles(this.inputStyles)
button("Submit")
.margin(1, em)
.padding(1, em)
.background("var(--main)")
.color("var(--accent)")
.border("1px solid var(--accent)")
})
.attr({action: "/login", method: "POST"})
.x(50, vw).y(50, vh)
.center()
})
.background("var(--main)")
.width(100, vw)
.height(100, vh)
}
}
register(Login)

View File

@@ -0,0 +1,38 @@
const IS_NODE =
typeof process !== "undefined" &&
process.versions?.node != null
async function bridgeSend(name, args) {
// Example browser implementation: send function call to server
const res = await global.Socket.send({
name: name,
args: args
})
const json = await res.json()
if (!res.ok) throw new Error(json.error)
return json.result
}
/**
* Wraps an object of functions so that:
* - Node calls the real function
* - Browser calls bridgeSend
*/
export function createBridge(funcs) {
return new Proxy(funcs, {
get(target, prop) {
const orig = target[prop]
if (typeof orig !== "function") return orig
return function (...args) {
if (IS_NODE) {
return orig(...args)
} else {
return bridgeSend(prop, args)
}
}
}
})
}

View File

@@ -0,0 +1,11 @@
import fs from "fs"
import { createBridge } from "./bridge.js"
const handlers = {
getProfile(one, two) {
fs.writeFileSync("output.txt", `${one} ${two}`)
return "written to disk"
},
}
export const { getProfile } = createBridge(handlers)

View File

@@ -0,0 +1,61 @@
class Connection {
connectionTries = 0;
ws;
receiveCB;
constructor(receiveCB) {
this.receiveCB = receiveCB;
}
init = async () => {
return new Promise((resolve, reject) => {
const url = window.location.hostname.includes("local")
? "ws://" + window.location.host
: "wss://" + window.location.hostname + window.location.pathname;
this.ws = new WebSocket(url);
this.ws.addEventListener('open', () => {
this.connectionTries = 0;
console.log("WebSocket connection established.");
this.ws.addEventListener('message', this.receiveCB);
resolve(this.ws); // resolve when open
});
this.ws.addEventListener('close', () => {
console.log('WebSocket closed');
this.checkOpen(); // attempt reconnection
});
this.ws.addEventListener('error', (err) => {
console.error('WebSocket error', err);
reject(err); // reject if error occurs
});
});
}
checkOpen = async () => {
if (this.ws.readyState === WebSocket.OPEN) {
return true;
} else {
await this.sleep(this.connectionTries < 20 ? 5000 : 60000);
this.connectionTries++;
console.log('Reestablishing connection');
await this.init();
}
}
sleep = (time) => new Promise(resolve => setTimeout(resolve, time));
send = (msg) => {
if (this.ws.readyState === WebSocket.OPEN) {
this.ws.send(msg);
} else if (this.connectionTries === 0) {
setTimeout(() => this.send(msg), 100);
} else {
console.error('No WebSocket connection: Cannot send message');
}
}
}
export default Connection;

49
src/_/code/ws/Socket.js Normal file
View File

@@ -0,0 +1,49 @@
import Connection from "./Connection.js";
export default class Socket {
connection;
disabled = true;
requestID = 1;
pending = new Map();
constructor() {
this.connection = new Connection(this.receive);
}
async init() {
await this.connection.init()
}
isOpen() {
if(this.connection.checkOpen()) {
return true;
} else {
return false;
}
}
send(msg) {
return new Promise(resolve => {
const id = (++this.requestID).toString();
this.pending.set(id, resolve);
this.connection.send(JSON.stringify({ id, ...msg }));
});
}
receive = (event) => {
const msg = JSON.parse(event.data);
if (msg.id && this.pending.has(msg.id)) {
this.pending.get(msg.id)(msg);
this.pending.delete(msg.id);
return;
} else {
this.onBroadcast(msg)
}
}
onBroadcast(msg) {
window.dispatchEvent(new CustomEvent(msg.event, {
detail: msg.msg
}));
}
}

1
src/_/code/ws/shim/fs.js Normal file
View File

@@ -0,0 +1 @@
export default {}

3
src/env.js Normal file
View File

@@ -0,0 +1,3 @@
class env {
BASE_URL = "https://parchment.page"
}

View File

@@ -1,3 +1,154 @@
import Socket from "/_/code/ws/Socket.js"
import "./Home.js" import "./Home.js"
Home() import "./Login.js"
document.body.style.backgroundColor = "var(--main)"
let Global = class {
Socket = new Socket()
profile = null
currentNetwork = ""
currentApp = ""
openApp = function(appName) {
const appUrl = appName.charAt(0).toLowerCase() + appName.slice(1);
let parts = window.location.pathname.split('/').filter(Boolean);
let newPath = "/" + parts[0] + "/" + appUrl
window.navigateTo(newPath)
const event = new CustomEvent('appchange', {
detail: { name: appName }
});
window.dispatchEvent(event)
}
async fetchAppData() {
let personalSpace = this.currentNetwork === this.profile
let appData = await fetch(`/api/${personalSpace ? "my" : "org"}data/` + this.currentNetwork.id, {method: "GET"})
let json = await appData.json()
return json
}
onNavigate = async () => {
let selectedNetwork = this.networkFromPath()
let selectedApp = this.appFromPath()
if(!selectedNetwork) {
if(this.profile.networks.length > 0) {
let path = `/${this.getDefaultNetworkName()}/${this.getDefaultAppName()}`
history.replaceState({}, '', path)
}
} else if(!selectedApp) {
if(this.currentNetwork === window.profile) {
history.replaceState({}, '', `${window.location.pathname}/${window.profile.apps[0]}`)
} else {
history.replaceState({}, '', `${window.location.pathname}/${this.getDefaultAppName()}`)
}
}
selectedNetwork = this.networkFromPath()
selectedApp = this.appFromPath()
let networkChanged = this.currentNetwork !== selectedNetwork
let appChanged = this.currentApp !== selectedApp
if(networkChanged) {
this.currentNetwork = selectedNetwork
this.currentApp = selectedApp
const event = new CustomEvent('networkchange', {
detail: { name: this.currentNetwork }
});
window.dispatchEvent(event)
}
if(!this.currentNetwork.data) {
this.currentNetwork.data = await this.fetchAppData()
}
if(appChanged && !networkChanged) {
this.currentApp = selectedApp
const event = new CustomEvent('appchange', {
detail: { name: this.currentApp }
});
window.dispatchEvent(event)
}
document.title = (this.currentNetwork === this.profile) ? "Parchment" : `${this.currentNetwork.abbreviation} | Parchment`
}
setCurrentNetworkAndApp() {
this.currentNetwork = this.networkFromPath()
}
getDefaultNetworkName() {
let defaultNetwork = this.profile.networks[0]
return defaultNetwork.abbreviation
}
getDefaultAppName() {
let defaultNetwork = this.profile.networks[0]
return defaultNetwork.apps[0].toLowerCase()
}
networkFromPath = function () {
const pathname = window.location.pathname;
const firstSegment = pathname.split('/').filter(Boolean)[0] || '';
if(firstSegment === "my") {
return this.profile
} else {
let networks = this.profile.networks
for(let i = 0; i < networks.length; i++) {
let network = networks[i]
if(network.abbreviation === firstSegment) {
return network
}
}
}
}
appFromPath = function() {
const pathname = window.location.pathname;
const segments = pathname.split('/').filter(Boolean);
const secondSegment = segments[1] || ""
const capitalized = secondSegment.charAt(0).toUpperCase() + secondSegment.slice(1);
return capitalized
}
async getProfile() {
try {
const res = await fetch("/profile", {
method: "GET",
credentials: "include",
headers: {
"Content-Type": "application/json"
}
});
if(res.status === 401) {
return res.status
}
if (!res.ok) throw new Error("Failed to fetch profile");
const profile = await res.json();
console.log("getProfile: ", profile);
this.profile = profile
} catch (err) {
console.error(err);
}
}
constructor() {
window.addEventListener("navigate", this.onNavigate)
this.getProfile().then(async (status) => {
if(status !== 401) {
console.log("it's legit")
await this.onNavigate()
Home()
} else {
Login()
}
})
}
}
window.global = new Global()

View File

@@ -8,6 +8,20 @@ export default defineConfig({
emptyOutDir: true, emptyOutDir: true,
}, },
server: { server: {
proxy: {
"/login": {
target: "http://localhost:10002",
changeOrigin: true
},
"/profile": {
target: "http://localhost:10002",
changeOrigin: true
},
"/api": {
target: "http://localhost:10002",
changeOrigin: true
}
},
host: true, host: true,
allowedHosts: ['sam.local'], allowedHosts: ['sam.local'],
} }