getting things going
This commit is contained in:
47
src/Login.js
Normal file
47
src/Login.js
Normal 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)
|
||||
38
src/_/code/bridge/bridge.js
Normal file
38
src/_/code/bridge/bridge.js
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
11
src/_/code/bridge/serverFunctions.js
Normal file
11
src/_/code/bridge/serverFunctions.js
Normal 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)
|
||||
61
src/_/code/ws/Connection.js
Normal file
61
src/_/code/ws/Connection.js
Normal 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
49
src/_/code/ws/Socket.js
Normal 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
1
src/_/code/ws/shim/fs.js
Normal file
@@ -0,0 +1 @@
|
||||
export default {}
|
||||
3
src/env.js
Normal file
3
src/env.js
Normal file
@@ -0,0 +1,3 @@
|
||||
class env {
|
||||
BASE_URL = "https://parchment.page"
|
||||
}
|
||||
153
src/index.js
153
src/index.js
@@ -1,3 +1,154 @@
|
||||
import Socket from "/_/code/ws/Socket.js"
|
||||
import "./Home.js"
|
||||
import "./Login.js"
|
||||
|
||||
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()
|
||||
document.body.style.backgroundColor = "var(--main)"
|
||||
} else {
|
||||
Login()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
window.global = new Global()
|
||||
@@ -8,6 +8,20 @@ export default defineConfig({
|
||||
emptyOutDir: true,
|
||||
},
|
||||
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,
|
||||
allowedHosts: ['sam.local'],
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user