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"
|
||||||
|
}
|
||||||
155
src/index.js
155
src/index.js
@@ -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()
|
||||||
@@ -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'],
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user