wip fix
This commit is contained in:
parent
38814dd5f1
commit
5f07237610
|
|
@ -179,3 +179,5 @@ app/web/.parcel-cache
|
||||||
app/static
|
app/static
|
||||||
.data
|
.data
|
||||||
.data/*
|
.data/*
|
||||||
|
|
||||||
|
timestamp.ts
|
||||||
|
|
@ -10,36 +10,36 @@ import { sworkerAddCache, sworkerRegister } from "./sworker-boot";
|
||||||
import { w } from "./utils/types/general";
|
import { w } from "./utils/types/general";
|
||||||
|
|
||||||
const start = async () => {
|
const start = async () => {
|
||||||
const base = `${location.protocol}//${location.host}`;
|
const base = `${location.protocol}//${location.host}`;
|
||||||
let react = {
|
let react = {
|
||||||
root: null as null | ReactRoot,
|
root: null as null | ReactRoot,
|
||||||
};
|
};
|
||||||
w.mobile = registerMobile();
|
w.mobile = registerMobile();
|
||||||
|
|
||||||
const cur = new URL(location.href);
|
const cur = new URL(location.href);
|
||||||
const base_url = `${cur.protocol}//${cur.host}`;
|
const base_url = `${cur.protocol}//${cur.host}`;
|
||||||
w.db = dbProxy(base_url);
|
w.db = dbProxy(base_url);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await loadApiProxyDef(base_url, false);
|
await loadApiProxyDef(base_url, false);
|
||||||
w.api = apiProxy(base_url);
|
w.api = apiProxy(base_url);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("Failed to load API:", base_url);
|
console.warn("Failed to load API:", base_url);
|
||||||
}
|
}
|
||||||
|
|
||||||
w.serverurl = base;
|
w.serverurl = base;
|
||||||
|
|
||||||
sworkerRegister(react);
|
sworkerRegister(react);
|
||||||
defineReact();
|
defineReact();
|
||||||
await defineWindow(false);
|
await defineWindow(false);
|
||||||
sworkerAddCache(base);
|
sworkerAddCache(base);
|
||||||
|
|
||||||
const el = document.getElementById("root");
|
const el = document.getElementById("root");
|
||||||
|
|
||||||
if (el) {
|
if (el) {
|
||||||
react.root = createRoot(el);
|
react.root = createRoot(el);
|
||||||
react.root.render(<Root />);
|
react.root.render(<Root />);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
start();
|
start();
|
||||||
|
|
|
||||||
|
|
@ -2,176 +2,235 @@ import { Root as ReactRoot } from "react-dom/client";
|
||||||
import { Root } from "./base/root";
|
import { Root } from "./base/root";
|
||||||
import { w } from "./utils/types/general";
|
import { w } from "./utils/types/general";
|
||||||
import { isLocalhost } from "./utils/ui/is-localhost";
|
import { isLocalhost } from "./utils/ui/is-localhost";
|
||||||
|
import { version } from "../timestamp";
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
updating: false,
|
||||||
|
};
|
||||||
|
|
||||||
export const sworkerRegister = async (react: { root: null | ReactRoot }) => {
|
export const sworkerRegister = async (react: { root: null | ReactRoot }) => {
|
||||||
if (navigator.serviceWorker) {
|
if (navigator.serviceWorker) {
|
||||||
if (!isLocalhost()) {
|
if (!isLocalhost()) {
|
||||||
const sw = await registerServiceWorker();
|
const sw = await registerServiceWorker();
|
||||||
const cacheCurrentPage = () => {
|
const cacheCurrentPage = () => {
|
||||||
const swController = navigator.serviceWorker.controller;
|
const swController = navigator.serviceWorker.controller;
|
||||||
if (swController) {
|
if (swController) {
|
||||||
[location.href, "", "/", "/ed", "/ed/_/_", "/login"].forEach(
|
[location.href, "", "/", "/ed", "/ed/_/_", "/login"].forEach(
|
||||||
(url) => {
|
(url) => {
|
||||||
swController.postMessage({
|
swController.postMessage({
|
||||||
type: "add-cache",
|
type: "add-cache",
|
||||||
url: url,
|
url: url,
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
cacheCurrentPage();
|
cacheCurrentPage();
|
||||||
navigator.serviceWorker.addEventListener("message", (e) => {
|
|
||||||
cacheCurrentPage();
|
|
||||||
if (react.root) {
|
|
||||||
if (e.data.type === "offline") {
|
|
||||||
w.offline = true;
|
|
||||||
const click = () => {
|
|
||||||
if (react.root) react.root.render(<Root />);
|
|
||||||
};
|
|
||||||
setTimeout(click, 5000);
|
|
||||||
react.root.render(
|
|
||||||
<>
|
|
||||||
<Root />
|
|
||||||
<div
|
|
||||||
className={cx(
|
|
||||||
css`
|
|
||||||
position: fixed;
|
|
||||||
bottom: 20px;
|
|
||||||
left: 0px;
|
|
||||||
right: 0px;
|
|
||||||
z-index: 999;
|
|
||||||
`,
|
|
||||||
"flex justify-center cursor-pointer"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="bg-orange-500 text-white px-4 py-2 rounded-full text-sm"
|
|
||||||
onClick={click}
|
|
||||||
>
|
|
||||||
Network Failed
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.data.type === "activated") {
|
const curver = localStorage.getItem("prasi-version");
|
||||||
if (e.data.shouldRefresh && sw) {
|
const swc = navigator.serviceWorker.controller;
|
||||||
react.root.render(
|
if (version !== curver && curver && react.root && swc) {
|
||||||
<>
|
react.root.render(
|
||||||
<Root />
|
<>
|
||||||
<div
|
<Root />
|
||||||
className={cx(
|
<div
|
||||||
css`
|
className={cx(
|
||||||
position: fixed;
|
css`
|
||||||
bottom: 20px;
|
position: fixed;
|
||||||
left: 0px;
|
bottom: 20px;
|
||||||
right: 0px;
|
left: 0px;
|
||||||
z-index: 999;
|
right: 0px;
|
||||||
`,
|
z-index: 999;
|
||||||
"flex justify-center"
|
`,
|
||||||
)}
|
"flex justify-center cursor-pointer",
|
||||||
>
|
)}
|
||||||
<div className="bg-blue-400 text-white px-4 py-2 rounded-full text-sm">
|
onClick={() => {
|
||||||
Updating App...
|
swc.postMessage({
|
||||||
</div>
|
type: "force-update",
|
||||||
</div>
|
});
|
||||||
</>
|
if (react.root)
|
||||||
);
|
react.root.render(
|
||||||
|
<>
|
||||||
sw.unregister().then(() => {
|
<Root />
|
||||||
window.location.reload();
|
<div
|
||||||
});
|
className={cx(
|
||||||
} else {
|
css`
|
||||||
const localVersion = localStorage.getItem("prasi-version");
|
|
||||||
if (localVersion !== e.data.version) {
|
|
||||||
localStorage.setItem("prasi-version", e.data.version);
|
|
||||||
const click = () => {
|
|
||||||
if (react.root) react.root.render(<Root />);
|
|
||||||
};
|
|
||||||
setTimeout(click, 5000);
|
|
||||||
react.root.render(
|
|
||||||
<>
|
|
||||||
<Root />
|
|
||||||
<div
|
|
||||||
className={cx(
|
|
||||||
css`
|
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 20px;
|
bottom: 20px;
|
||||||
left: 0px;
|
left: 0px;
|
||||||
right: 0px;
|
right: 0px;
|
||||||
z-index: 999;
|
z-index: 999;
|
||||||
`,
|
`,
|
||||||
"flex justify-center cursor-pointer"
|
"flex justify-center",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div
|
<div className="bg-blue-400 text-white px-4 py-2 rounded-full text-sm">
|
||||||
className="bg-green-600 text-white px-4 py-2 rounded-full text-sm"
|
Updating App...
|
||||||
onClick={click}
|
</div>
|
||||||
>
|
</div>
|
||||||
Prasi Updated{" "}
|
</>,
|
||||||
<span className="opacity-50">{e.data.version}</span>
|
);
|
||||||
</div>
|
}}
|
||||||
</div>
|
>
|
||||||
</>
|
<div className="bg-orange-600 text-white px-4 py-2 rounded-full text-sm select-none">
|
||||||
);
|
New Version Available. Click to Update
|
||||||
}
|
</div>
|
||||||
}
|
</div>
|
||||||
}
|
</>,
|
||||||
}
|
);
|
||||||
});
|
}
|
||||||
} else {
|
|
||||||
navigator.serviceWorker.getRegistrations().then(function (registrations) {
|
navigator.serviceWorker.addEventListener("message", (e) => {
|
||||||
for (let registration of registrations) {
|
cacheCurrentPage();
|
||||||
registration.unregister();
|
if (react.root) {
|
||||||
}
|
if (e.data.type === "offline") {
|
||||||
});
|
w.offline = true;
|
||||||
}
|
const click = () => {
|
||||||
}
|
if (react.root) react.root.render(<Root />);
|
||||||
|
};
|
||||||
|
setTimeout(click, 5000);
|
||||||
|
react.root.render(
|
||||||
|
<>
|
||||||
|
<Root />
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
css`
|
||||||
|
position: fixed;
|
||||||
|
bottom: 20px;
|
||||||
|
left: 0px;
|
||||||
|
right: 0px;
|
||||||
|
z-index: 999;
|
||||||
|
`,
|
||||||
|
"flex justify-center cursor-pointer",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="bg-orange-500 text-white px-4 py-2 rounded-full text-sm"
|
||||||
|
onClick={click}
|
||||||
|
>
|
||||||
|
Network Failed
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.data.type === "activated") {
|
||||||
|
if (e.data.shouldRefresh && sw) {
|
||||||
|
react.root.render(
|
||||||
|
<>
|
||||||
|
<Root />
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
css`
|
||||||
|
position: fixed;
|
||||||
|
bottom: 20px;
|
||||||
|
left: 0px;
|
||||||
|
right: 0px;
|
||||||
|
z-index: 999;
|
||||||
|
`,
|
||||||
|
"flex justify-center",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="bg-blue-400 text-white px-4 py-2 rounded-full text-sm">
|
||||||
|
Updating App...
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>,
|
||||||
|
);
|
||||||
|
|
||||||
|
sw.unregister().then(() => {
|
||||||
|
window.location.reload();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const localVersion = localStorage.getItem("prasi-version");
|
||||||
|
if (localVersion !== e.data.version) {
|
||||||
|
localStorage.setItem("prasi-version", e.data.version);
|
||||||
|
const click = () => {
|
||||||
|
if (react.root) react.root.render(<Root />);
|
||||||
|
};
|
||||||
|
setTimeout(click, 5000);
|
||||||
|
react.root.render(
|
||||||
|
<>
|
||||||
|
<Root />
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
css`
|
||||||
|
position: fixed;
|
||||||
|
bottom: 20px;
|
||||||
|
left: 0px;
|
||||||
|
right: 0px;
|
||||||
|
z-index: 999;
|
||||||
|
`,
|
||||||
|
"flex justify-center cursor-pointer",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="bg-green-600 text-white px-4 py-2 rounded-full text-sm"
|
||||||
|
onClick={click}
|
||||||
|
>
|
||||||
|
Prasi Updated{" "}
|
||||||
|
<span className="opacity-50">{e.data.version}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
navigator.serviceWorker.getRegistrations().then(function (registrations) {
|
||||||
|
for (let registration of registrations) {
|
||||||
|
registration.unregister();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const registerServiceWorker = async () => {
|
const registerServiceWorker = async () => {
|
||||||
if ("serviceWorker" in navigator) {
|
if ("serviceWorker" in navigator) {
|
||||||
try {
|
try {
|
||||||
return await navigator.serviceWorker.register(
|
return await navigator.serviceWorker.register(
|
||||||
new URL("./sworker.ts", import.meta.url),
|
new URL("./sworker.ts", import.meta.url),
|
||||||
{
|
{
|
||||||
type: "module",
|
type: "module",
|
||||||
scope: "/",
|
scope: "/",
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Registration failed with ${error}`);
|
console.error(`Registration failed with ${error}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const sworkerAddCache = (base: string) => {
|
export const sworkerAddCache = (base: string) => {
|
||||||
if (navigator.serviceWorker) {
|
if (navigator.serviceWorker) {
|
||||||
if (!isLocalhost()) {
|
if (!isLocalhost()) {
|
||||||
const swc = navigator.serviceWorker.controller;
|
const swc = navigator.serviceWorker.controller;
|
||||||
if (swc) {
|
if (swc) {
|
||||||
[location.href, "", "/", "/ed", "/ed/_/_", "/login"].forEach((url) => {
|
[location.href, "", "/", "/ed", "/ed/_/_", "/login"].forEach((url) => {
|
||||||
swc.postMessage({
|
swc.postMessage({
|
||||||
type: "add-cache",
|
type: "add-cache",
|
||||||
url: url,
|
url: url,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
if (w.prasiApi && w.prasiApi[base] && w.prasiApi[base].apiEntry) {
|
if (w.prasiApi && w.prasiApi[base] && w.prasiApi[base].apiEntry) {
|
||||||
const routes = Object.entries(w.prasiApi[base].apiEntry).map(
|
const routes = Object.entries(w.prasiApi[base].apiEntry).map(
|
||||||
([k, v]: any) => ({
|
([k, v]: any) => ({
|
||||||
url: v.url,
|
url: v.url,
|
||||||
name: k,
|
name: k,
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
swc.postMessage({
|
swc.postMessage({
|
||||||
type: "define-route",
|
type: "define-route",
|
||||||
routes,
|
routes,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,95 +1,110 @@
|
||||||
import { manifest, version } from "@parcel/service-worker";
|
import { manifest } from "@parcel/service-worker";
|
||||||
import { RadixRouter, createRouter } from "radix3";
|
import { RadixRouter, createRouter } from "radix3";
|
||||||
|
import { version } from "../timestamp";
|
||||||
|
|
||||||
const g = {
|
const g = {
|
||||||
router: null as null | RadixRouter<any>,
|
router: null as null | RadixRouter<any>,
|
||||||
offline: false,
|
offline: false,
|
||||||
broadcast(msg: any) {
|
broadcast(msg: any) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const c: Clients = self.clients;
|
const c: Clients = self.clients;
|
||||||
c.matchAll({ includeUncontrolled: true }).then((clients) => {
|
c.matchAll({ includeUncontrolled: true }).then((clients) => {
|
||||||
clients.forEach((client) => {
|
clients.forEach((client) => {
|
||||||
client.postMessage(msg);
|
client.postMessage(msg);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
async function install() {
|
async function install() {
|
||||||
const cache = await caches.open(version);
|
const cache = await caches.open(version);
|
||||||
await cache.addAll(manifest);
|
await cache.addAll(manifest);
|
||||||
g.broadcast({ type: "installed" });
|
g.broadcast({ type: "installed" });
|
||||||
}
|
}
|
||||||
addEventListener("install", (e) => (e as ExtendableEvent).waitUntil(install()));
|
addEventListener("install", (e) => (e as ExtendableEvent).waitUntil(install()));
|
||||||
|
|
||||||
async function activate() {
|
async function activate() {
|
||||||
let shouldRefresh = false;
|
let shouldRefresh = false;
|
||||||
if (!g.offline) {
|
if (!g.offline) {
|
||||||
const keys = await caches.keys();
|
const keys = await caches.keys();
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
keys.map(async (key) => {
|
keys.map(async (key) => {
|
||||||
if (key !== version) {
|
if (key !== version) {
|
||||||
await caches.delete(key);
|
await caches.delete(key);
|
||||||
shouldRefresh = true;
|
shouldRefresh = true;
|
||||||
}
|
}
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
g.broadcast({ type: "activated", shouldRefresh, version });
|
g.broadcast({ type: "activated", shouldRefresh, version });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
addEventListener("activate", (e) =>
|
addEventListener("activate", (e) =>
|
||||||
(e as ExtendableEvent).waitUntil(activate())
|
(e as ExtendableEvent).waitUntil(activate()),
|
||||||
);
|
);
|
||||||
|
|
||||||
addEventListener("fetch", async (evt) => {
|
addEventListener("fetch", async (evt) => {
|
||||||
const e = evt as FetchEvent;
|
const e = evt as FetchEvent;
|
||||||
|
|
||||||
const url = new URL(e.request.url);
|
const url = new URL(e.request.url);
|
||||||
|
|
||||||
if (g.router) {
|
if (g.router) {
|
||||||
const found = g.router.lookup(url.pathname);
|
const found = g.router.lookup(url.pathname);
|
||||||
if (found) {
|
if (found) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
e.respondWith(
|
e.respondWith(
|
||||||
(async () => {
|
(async () => {
|
||||||
const r = await caches.match(e.request);
|
const r = await caches.match(e.request);
|
||||||
if (r) {
|
if (r) {
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
g.offline = false;
|
g.offline = false;
|
||||||
return await fetch(e.request);
|
return await fetch(e.request);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
g.offline = true;
|
g.offline = true;
|
||||||
g.broadcast({ type: "offline" });
|
g.broadcast({ type: "offline" });
|
||||||
return new Response();
|
return new Response();
|
||||||
}
|
}
|
||||||
})()
|
})(),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
addEventListener("message", async (e) => {
|
addEventListener("message", async (e) => {
|
||||||
const type = e.data.type;
|
const type = e.data.type;
|
||||||
const cache = await caches.open(version);
|
const cache = await caches.open(version);
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "add-cache":
|
case "add-cache":
|
||||||
{
|
{
|
||||||
const cached = await cache.match(e.data.url);
|
const cached = await cache.match(e.data.url);
|
||||||
if (!cached) {
|
if (!cached) {
|
||||||
await cache.add(e.data.url);
|
await cache.add(e.data.url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "define-route":
|
case "define-route":
|
||||||
g.router = createRouter({ strictTrailingSlash: false });
|
g.router = createRouter({ strictTrailingSlash: false });
|
||||||
for (const route of e.data.routes) {
|
for (const route of e.data.routes) {
|
||||||
g.router.insert(route.url, route);
|
g.router.insert(route.url, route);
|
||||||
}
|
}
|
||||||
await activate();
|
await activate();
|
||||||
break;
|
break;
|
||||||
}
|
case "force-update":
|
||||||
|
{
|
||||||
|
const keys = await caches.keys();
|
||||||
|
await Promise.all(
|
||||||
|
keys.map(async (key) => {
|
||||||
|
if (key !== version) {
|
||||||
|
await caches.delete(key);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
await install();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ export const isLocalhost = () => {
|
||||||
"localhost",
|
"localhost",
|
||||||
"127.0.0.1",
|
"127.0.0.1",
|
||||||
"192.168",
|
"192.168",
|
||||||
"trycloudflare.com",
|
// "trycloudflare.com",
|
||||||
"ngrok",
|
"ngrok",
|
||||||
].find((e) => location.hostname.includes(e));
|
].find((e) => location.hostname.includes(e));
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,103 +1,101 @@
|
||||||
|
import { createId } from "@paralleldrive/cuid2";
|
||||||
import brotliPromise from "brotli-wasm";
|
import brotliPromise from "brotli-wasm";
|
||||||
import { spawn } from "bun";
|
import { spawn } from "bun";
|
||||||
import { dir } from "dir";
|
import { dir } from "dir";
|
||||||
import { fdir } from "fdir";
|
import { fdir } from "fdir";
|
||||||
import { statSync } from "fs";
|
import { existsAsync, listAsync, removeAsync, writeAsync } from "fs-jetpack";
|
||||||
import {
|
|
||||||
copyAsync,
|
|
||||||
existsAsync,
|
|
||||||
listAsync,
|
|
||||||
removeAsync,
|
|
||||||
writeAsync,
|
|
||||||
} from "fs-jetpack";
|
|
||||||
const brotli = await brotliPromise;
|
const brotli = await brotliPromise;
|
||||||
|
|
||||||
await removeAsync(dir.path("app/web/.parcel-cache"));
|
await removeAsync(dir.path("app/web/.parcel-cache"));
|
||||||
await removeAsync(dir.path("app/static"));
|
await removeAsync(dir.path("app/static"));
|
||||||
|
|
||||||
|
await writeAsync(
|
||||||
|
dir.path("app/web/timestamp.ts"),
|
||||||
|
`export const version = "${createId().substring(0, 7)}";`,
|
||||||
|
);
|
||||||
|
|
||||||
const args = [
|
const args = [
|
||||||
"node",
|
"node",
|
||||||
dir.path("node_modules/.bin/parcel"),
|
dir.path("node_modules/.bin/parcel"),
|
||||||
"build",
|
"build",
|
||||||
"./src/index.tsx",
|
"./src/index.tsx",
|
||||||
// "--no-optimize",
|
// "--no-optimize",
|
||||||
"--no-scope-hoist",
|
"--no-scope-hoist",
|
||||||
"--dist-dir",
|
"--dist-dir",
|
||||||
dir.path(`app/static`),
|
dir.path(`app/static`),
|
||||||
];
|
];
|
||||||
|
|
||||||
const parcel = spawn({
|
const parcel = spawn({
|
||||||
cmd: args,
|
cmd: args,
|
||||||
cwd: dir.path("app/web"),
|
cwd: dir.path("app/web"),
|
||||||
stdio: ["ignore", "inherit", "inherit"],
|
stdio: ["ignore", "inherit", "inherit"],
|
||||||
});
|
});
|
||||||
await parcel.exited;
|
await parcel.exited;
|
||||||
|
|
||||||
const public_br = dir.path("app/web/public-br");
|
const public_br = dir.path("app/web/public-br");
|
||||||
if (!(await existsAsync(public_br))) {
|
if (!(await existsAsync(public_br))) {
|
||||||
const api = new fdir().withRelativePaths().crawl(dir.path("app/web/public"));
|
const api = new fdir().withRelativePaths().crawl(dir.path("app/web/public"));
|
||||||
const files = api.sync();
|
const files = api.sync();
|
||||||
if (files) {
|
if (files) {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
files.map(async (file) => {
|
files.map(async (file) => {
|
||||||
const br = brotli.compress(
|
const br = brotli.compress(
|
||||||
new Uint8Array(
|
new Uint8Array(
|
||||||
await Bun.file(dir.path(`app/web/public/${file}`)).arrayBuffer()
|
await Bun.file(dir.path(`app/web/public/${file}`)).arrayBuffer(),
|
||||||
),
|
),
|
||||||
{ quality: 11 }
|
{ quality: 11 },
|
||||||
);
|
);
|
||||||
if (br) {
|
if (br) {
|
||||||
console.log(`Compressing [public] ${file}`);
|
console.log(`Compressing [public] ${file}`);
|
||||||
await writeAsync(
|
await writeAsync(
|
||||||
dir.path(`app/web/public-br/${file}`),
|
dir.path(`app/web/public-br/${file}`),
|
||||||
Buffer.from(br)
|
Buffer.from(br),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const static_br = dir.path("app/static-br");
|
const static_br = dir.path("app/static-br");
|
||||||
await removeAsync(static_br);
|
await removeAsync(static_br);
|
||||||
const files = await listAsync(dir.path("app/static"));
|
const files = await listAsync(dir.path("app/static"));
|
||||||
if (files) {
|
if (files) {
|
||||||
await Promise.all(
|
// await Promise.all(
|
||||||
files
|
// files
|
||||||
.filter((file) => statSync(dir.path(`app/static/${file}`)).isFile())
|
// .filter((file) => statSync(dir.path(`app/static/${file}`)).isFile())
|
||||||
.map(async (file) => {
|
// .map(async (file) => {
|
||||||
if (!(await Bun.file(dir.path(`app/static-br/${file}`)).exists())) {
|
// if (!(await Bun.file(dir.path(`app/static-br/${file}`)).exists())) {
|
||||||
const br = brotli.compress(
|
// const br = brotli.compress(
|
||||||
new Uint8Array(
|
// new Uint8Array(
|
||||||
await Bun.file(dir.path(`app/static/${file}`)).arrayBuffer()
|
// await Bun.file(dir.path(`app/static/${file}`)).arrayBuffer(),
|
||||||
),
|
// ),
|
||||||
{ quality: 11 }
|
// { quality: 11 },
|
||||||
);
|
// );
|
||||||
if (br) {
|
// if (br) {
|
||||||
console.log(`Compressing [static] ${file}`);
|
// console.log(`Compressing [static] ${file}`);
|
||||||
await writeAsync(
|
// await writeAsync(
|
||||||
dir.path(`app/static-br/${file}`),
|
// dir.path(`app/static-br/${file}`),
|
||||||
Buffer.from(br)
|
// Buffer.from(br),
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
})
|
// }),
|
||||||
);
|
// );
|
||||||
|
// const pub = await listAsync(dir.path("app/web/public-br"));
|
||||||
const pub = await listAsync(dir.path("app/web/public-br"));
|
// if (pub) {
|
||||||
if (pub) {
|
// await Promise.all(
|
||||||
await Promise.all(
|
// pub.map(async (file) => {
|
||||||
pub.map(async (file) => {
|
// if (await existsAsync(`app/static-br/${file}`)) {
|
||||||
if (await existsAsync(`app/static-br/${file}`)) {
|
// await removeAsync(`app/static-br/${file}`);
|
||||||
await removeAsync(`app/static-br/${file}`);
|
// }
|
||||||
}
|
// await copyAsync(
|
||||||
await copyAsync(
|
// dir.path(`app/web/public-br/${file}`),
|
||||||
dir.path(`app/web/public-br/${file}`),
|
// dir.path(`app/static-br/${file}`),
|
||||||
dir.path(`app/static-br/${file}`)
|
// );
|
||||||
);
|
// }),
|
||||||
})
|
// );
|
||||||
);
|
// }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await import("./build-site");
|
await import("./build-site");
|
||||||
|
|
|
||||||
|
|
@ -9,58 +9,66 @@ import { syncActionDefinition } from "utils/sync-def";
|
||||||
import { user } from "../../app/srv/ws/sync/entity/user";
|
import { user } from "../../app/srv/ws/sync/entity/user";
|
||||||
import { snapshot } from "../../app/srv/ws/sync/entity/snapshot";
|
import { snapshot } from "../../app/srv/ws/sync/entity/snapshot";
|
||||||
import { initSrv } from "../../app/srv/init";
|
import { initSrv } from "../../app/srv/init";
|
||||||
|
import { createId } from "@paralleldrive/cuid2";
|
||||||
import { prepareApiRoutes } from "./server/api/api-scan";
|
import { prepareApiRoutes } from "./server/api/api-scan";
|
||||||
|
import { writeAsync } from "fs-jetpack";
|
||||||
|
import { dir } from "dir";
|
||||||
|
|
||||||
g.status = "init";
|
g.status = "init";
|
||||||
|
|
||||||
|
await writeAsync(
|
||||||
|
dir.path("app/web/timestamp.ts"),
|
||||||
|
`export const version = "${createId().substring(0, 7)}";`,
|
||||||
|
);
|
||||||
|
|
||||||
if (!g.Y) {
|
if (!g.Y) {
|
||||||
g.Y = await import("yjs");
|
g.Y = await import("yjs");
|
||||||
g.syncronize = (await import("y-pojo")).syncronize;
|
g.syncronize = (await import("y-pojo")).syncronize;
|
||||||
|
|
||||||
await createLogger();
|
await createLogger();
|
||||||
g.api = {};
|
g.api = {};
|
||||||
g.mode = process.argv.includes("dev") ? "dev" : "prod";
|
g.mode = process.argv.includes("dev") ? "dev" : "prod";
|
||||||
g.datadir = g.mode == "prod" ? "../data" : "data";
|
g.datadir = g.mode == "prod" ? "../data" : "data";
|
||||||
g.port = parseInt(process.env.PORT || "4550");
|
g.port = parseInt(process.env.PORT || "4550");
|
||||||
|
|
||||||
g.log.info(g.mode === "dev" ? "DEVELOPMENT" : "PRODUCTION");
|
g.log.info(g.mode === "dev" ? "DEVELOPMENT" : "PRODUCTION");
|
||||||
if (g.mode === "dev") {
|
if (g.mode === "dev") {
|
||||||
await startDevWatcher();
|
await startDevWatcher();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** init lmdb */
|
/** init lmdb */
|
||||||
user.conf.init();
|
user.conf.init();
|
||||||
snapshot.init();
|
snapshot.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
const db = g.db;
|
const db = g.db;
|
||||||
if (!db) {
|
if (!db) {
|
||||||
await preparePrisma();
|
await preparePrisma();
|
||||||
await ensureNotRunning();
|
await ensureNotRunning();
|
||||||
const db = g.db;
|
const db = g.db;
|
||||||
if (db) {
|
if (db) {
|
||||||
db.$connect()
|
db.$connect()
|
||||||
.catch((e: any) => {
|
.catch((e: any) => {
|
||||||
g.log.error(`[DB ERROR]\n${e.message}`);
|
g.log.error(`[DB ERROR]\n${e.message}`);
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
g.log.info("Database connected");
|
g.log.info("Database connected");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!g.apiPrepared) {
|
if (!g.apiPrepared) {
|
||||||
await initSrv();
|
await initSrv();
|
||||||
await syncActionDefinition();
|
await syncActionDefinition();
|
||||||
g.log.info("WS Action defined");
|
g.log.info("WS Action defined");
|
||||||
await prepareApiRoutes();
|
await prepareApiRoutes();
|
||||||
await prepareAPITypes();
|
await prepareAPITypes();
|
||||||
g.log.info("API Prepared");
|
g.log.info("API Prepared");
|
||||||
g.apiPrepared = true;
|
g.apiPrepared = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!g.parcel) {
|
if (!g.parcel) {
|
||||||
await parcelBuild();
|
await parcelBuild();
|
||||||
}
|
}
|
||||||
|
|
||||||
const { createServer } = await import("./server/create");
|
const { createServer } = await import("./server/create");
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { dir } from "dir";
|
import { dir } from "dir";
|
||||||
import { inspectTreeAsync } from "fs-jetpack";
|
import { exists, inspectTreeAsync } from "fs-jetpack";
|
||||||
import { InspectTreeResult } from "fs-jetpack/types";
|
import { InspectTreeResult } from "fs-jetpack/types";
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
import { watch } from "fs";
|
import { watch } from "fs";
|
||||||
|
|
@ -8,83 +8,88 @@ import mime from "mime";
|
||||||
import { g } from "utils/global";
|
import { g } from "utils/global";
|
||||||
|
|
||||||
const web = {
|
const web = {
|
||||||
get path() {
|
brExists: null as null | boolean,
|
||||||
if (g.mode === "dev") return "static";
|
get path() {
|
||||||
return "static-br";
|
if (g.mode === "dev") return "static";
|
||||||
},
|
if (this.brExists === null) {
|
||||||
|
this.brExists = !!exists(dir.path("app/static-br"));
|
||||||
|
}
|
||||||
|
if (this.brExists) return "static-br";
|
||||||
|
else return "static";
|
||||||
|
},
|
||||||
};
|
};
|
||||||
const cache = {
|
const cache = {
|
||||||
static: {} as Record<
|
static: {} as Record<
|
||||||
string,
|
string,
|
||||||
{ type: string; content: any; compression: "" | "br" }
|
{ type: string; content: any; compression: "" | "br" }
|
||||||
>,
|
>,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const serveStatic = {
|
export const serveStatic = {
|
||||||
init: async () => {
|
init: async () => {
|
||||||
const list = await inspectTreeAsync(dir.path(`app/${web.path}`));
|
const list = await inspectTreeAsync(dir.path(`app/${web.path}`));
|
||||||
const walk = async (
|
const walk = async (
|
||||||
list: InspectTreeResult,
|
list: InspectTreeResult,
|
||||||
parent?: InspectTreeResult[]
|
parent?: InspectTreeResult[],
|
||||||
) => {
|
) => {
|
||||||
if (list.type === "dir") {
|
if (list.type === "dir") {
|
||||||
for (const item of list.children) {
|
for (const item of list.children) {
|
||||||
await walk(item, [...(parent || []), list]);
|
await walk(item, [...(parent || []), list]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const path = join(...(parent || []).map((e) => e.name), list.name);
|
const path = join(...(parent || []).map((e) => e.name), list.name);
|
||||||
const file = Bun.file(dir.path(`app/${path}`));
|
const file = Bun.file(dir.path(`app/${path}`));
|
||||||
if (await file.exists()) {
|
if (await file.exists()) {
|
||||||
cache.static[path.substring(web.path.length)] = {
|
cache.static[path.substring(web.path.length)] = {
|
||||||
type: mime.getType(path) || "application/octet-stream",
|
type: mime.getType(path) || "application/octet-stream",
|
||||||
compression: g.mode === "prod" ? "br" : "",
|
compression: g.mode === "prod" ? "br" : "",
|
||||||
content: await file.arrayBuffer(),
|
content: await file.arrayBuffer(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (list) {
|
if (list) {
|
||||||
await walk(list);
|
await walk(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (g.mode === "dev") {
|
if (g.mode === "dev") {
|
||||||
watch(dir.path(`app/static`), async (_, filename) => {
|
watch(dir.path(`app/static`), async (_, filename) => {
|
||||||
if (filename) {
|
if (filename) {
|
||||||
const path = join("static", filename);
|
const path = join("static", filename);
|
||||||
const file = Bun.file(dir.path(`app/${path}`));
|
const file = Bun.file(dir.path(`app/${path}`));
|
||||||
if (await file.exists()) {
|
if (await file.exists()) {
|
||||||
cache.static[`/${filename}`] = {
|
cache.static[`/${filename}`] = {
|
||||||
type: mime.getType(path) || "application/octet-stream",
|
type: mime.getType(path) || "application/octet-stream",
|
||||||
compression: g.mode === "prod" ? "br" : "",
|
compression: g.mode === "prod" ? "br" : "",
|
||||||
content: await file.arrayBuffer(),
|
content: await file.arrayBuffer(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
exists: (url: URL) => {
|
exists: (url: URL) => {
|
||||||
return !!cache.static[url.pathname];
|
return !!cache.static[url.pathname];
|
||||||
},
|
},
|
||||||
serve: (url: URL) => {
|
serve: (url: URL) => {
|
||||||
let file = cache.static[url.pathname];
|
let file = cache.static[url.pathname];
|
||||||
if (file) {
|
if (file) {
|
||||||
return new Response(file.content, {
|
return new Response(file.content, {
|
||||||
headers: {
|
headers: {
|
||||||
...{ "content-type": file.type },
|
...{ "content-type": file.type },
|
||||||
...(file.compression ? { "content-encoding": file.compression } : {}),
|
...(file.compression ? { "content-encoding": file.compression } : {}),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
file = cache.static["/index.html"];
|
file = cache.static["/index.html"];
|
||||||
if (file) {
|
if (file) {
|
||||||
return new Response(file.content, {
|
return new Response(file.content, {
|
||||||
headers: {
|
headers: {
|
||||||
...{ "content-type": file.type },
|
...{ "content-type": file.type },
|
||||||
...(file.compression ? { "content-encoding": file.compression } : {}),
|
...(file.compression ? { "content-encoding": file.compression } : {}),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,22 @@
|
||||||
import { $ } from "execa";
|
import { $ } from "execa";
|
||||||
import { existsAsync } from "fs-jetpack";
|
import { exists, existsAsync } from "fs-jetpack";
|
||||||
import { dir } from "./dir";
|
import { dir } from "./dir";
|
||||||
import { g } from "./global";
|
import { g } from "./global";
|
||||||
|
|
||||||
export const preparePrisma = async () => {
|
export const preparePrisma = async () => {
|
||||||
if (
|
if (
|
||||||
(await existsAsync(dir.path("app/db/.env"))) ||
|
(await existsAsync(dir.path("app/db/.env"))) ||
|
||||||
process.env.DATABASE_URL
|
process.env.DATABASE_URL
|
||||||
) {
|
) {
|
||||||
if (g.mode === "prod") {
|
if (g.mode === "prod" && exists(dir.path("app/static-br"))) {
|
||||||
g.log.info("Prisma: db pull & generate");
|
g.log.info("Prisma: db pull & generate");
|
||||||
await $({ cwd: dir.path(`app/db`) })`bun prisma db pull`;
|
await $({ cwd: dir.path(`app/db`) })`bun prisma db pull`;
|
||||||
await $({ cwd: dir.path(`app/db`) })`bun prisma generate`;
|
await $({ cwd: dir.path(`app/db`) })`bun prisma generate`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { PrismaClient } = await import("../../../app/db/db");
|
const { PrismaClient } = await import("../../../app/db/db");
|
||||||
g.db = new PrismaClient();
|
g.db = new PrismaClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
g.dburl = process.env.DATABASE_URL || "";
|
g.dburl = process.env.DATABASE_URL || "";
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue