diff --git a/app/web/src/base/page/all.tsx b/app/web/src/base/page/all.tsx
index 4a19a9ed..428ebc33 100644
--- a/app/web/src/base/page/all.tsx
+++ b/app/web/src/base/page/all.tsx
@@ -6,8 +6,9 @@ export default page({
url: "**",
component: ({}) => {
useEffect(() => {
+
if (localStorage.getItem("prasi-session")) {
- navigate("/editor");
+ navigate("/editor/");
} else {
navigate("/login");
}
diff --git a/app/web/src/base/page/editor.tsx b/app/web/src/base/page/editor.tsx
index 0d04e743..bd03f58c 100644
--- a/app/web/src/base/page/editor.tsx
+++ b/app/web/src/base/page/editor.tsx
@@ -135,10 +135,13 @@ export default page({
const Editor = local.Editor;
if (local.loading || !Editor) return ;
- navigator.serviceWorker.controller?.postMessage({
- type: "add-cache",
- url: location.href,
- });
+ const sw = navigator.serviceWorker.controller;
+ if (sw) {
+ sw.postMessage({
+ type: "add-cache",
+ url: location.href,
+ });
+ }
return (
diff --git a/app/web/src/index.tsx b/app/web/src/index.tsx
index eb425061..e069c362 100644
--- a/app/web/src/index.tsx
+++ b/app/web/src/index.tsx
@@ -6,7 +6,8 @@ import { createAPI, createDB, reloadDBAPI } from "./utils/script/init-api";
import { w } from "./utils/types/general";
const start = async () => {
- registerServiceWorker();
+ const sw = await registerServiceWorker();
+
defineReact();
await defineWindow(false);
const base = `${location.protocol}//${location.host}`;
@@ -15,6 +16,37 @@ const start = async () => {
w.api = createAPI(base);
w.db = createDB(base);
+ navigator.serviceWorker.addEventListener("message", (e) => {
+ if (e.data.type === "activated") {
+ if (e.data.shouldRefresh && sw) {
+ sw.unregister().then(() => {
+ window.location.reload();
+ });
+ }
+ }
+ if (e.data.type === "ready") {
+ const sw = navigator.serviceWorker.controller;
+
+ if (sw) {
+ const routes = Object.entries(w.prasiApi[base].apiEntry).map(
+ ([k, v]: any) => ({
+ url: v.url,
+ name: k,
+ })
+ );
+
+ sw.postMessage({
+ type: "add-cache",
+ url: location.href,
+ });
+ sw.postMessage({
+ type: "define-route",
+ routes,
+ });
+ }
+ }
+ });
+
const el = document.getElementById("root");
if (el) {
createRoot(el).render();
@@ -24,7 +56,7 @@ const start = async () => {
const registerServiceWorker = async () => {
if ("serviceWorker" in navigator) {
try {
- await navigator.serviceWorker.register(
+ return await navigator.serviceWorker.register(
new URL("./sworker.ts", import.meta.url),
{
type: "module",
diff --git a/app/web/src/render/editor/panel/toolbar/center/api/Internal.tsx b/app/web/src/render/editor/panel/toolbar/center/api/Internal.tsx
index c2437806..2d4bbfc5 100644
--- a/app/web/src/render/editor/panel/toolbar/center/api/Internal.tsx
+++ b/app/web/src/render/editor/panel/toolbar/center/api/Internal.tsx
@@ -23,7 +23,7 @@ export const InternalAPI: FC<{
const reloadStatus = () => {
if (p.site) {
const s = api.srvapi_check.bind({ apiUrl: "https://api.prasi.app" });
- s(p.site.id).then((e) => {
+ s(p.site.id).then((e: any) => {
local.status = e;
checkApi(e === "started");
local.render();
diff --git a/app/web/src/sworker.ts b/app/web/src/sworker.ts
index bcf6165c..f4d4a85f 100644
--- a/app/web/src/sworker.ts
+++ b/app/web/src/sworker.ts
@@ -1,21 +1,38 @@
import { manifest, version } from "@parcel/service-worker";
-
+import { RadixRouter, createRouter } from "radix3";
const g = {
- cache: null as null | Cache,
- dev: false,
- baseUrl: "",
+ router: null as null | RadixRouter,
+ broadcast(msg: any) {
+ // @ts-ignore
+ const c: Clients = self.clients;
+ c.matchAll({ includeUncontrolled: true }).then((clients) => {
+ clients.forEach((client) => {
+ client.postMessage(msg);
+ });
+ });
+ },
};
async function install() {
const cache = await caches.open(version);
- g.cache = cache;
await cache.addAll(manifest);
+ g.broadcast({ type: "installed" });
}
addEventListener("install", (e) => (e as ExtendableEvent).waitUntil(install()));
async function activate() {
+ let shouldRefresh = false;
const keys = await caches.keys();
- await Promise.all(keys.map((key) => key !== version && caches.delete(key)));
+ await Promise.all(
+ keys.map(async (key) => {
+ if (key !== version) {
+ await caches.delete(key);
+ shouldRefresh = true;
+ }
+ })
+ );
+
+ g.broadcast({ type: "activated", shouldRefresh });
}
addEventListener("activate", (e) =>
(e as ExtendableEvent).waitUntil(activate())
@@ -24,44 +41,29 @@ addEventListener("activate", (e) =>
addEventListener("fetch", async (evt) => {
const e = evt as FetchEvent;
- if (g.baseUrl) {
- const url = e.request.url;
- if (url.startsWith(g.baseUrl)) {
+ const url = new URL(e.request.url);
+
+ if (g.router) {
+ const found = g.router.lookup(url.pathname);
+ if (found) {
return;
}
}
e.respondWith(
(async () => {
- if (!g.cache) {
- g.cache = await caches.open(version);
- }
-
- if (!g.baseUrl) {
- const keys = await g.cache.keys();
- const url = new URL(keys[0].url);
- url.pathname = "";
- g.baseUrl = url.toString();
- }
-
- const cache = g.cache;
-
- const r = await cache.match(e.request);
+ const r = await caches.match(e.request);
if (r) {
- cache.add(e.request);
return r;
}
- return await fetch(e.request.url);
+ return fetch(e.request);
})()
);
});
+g.broadcast({ type: "ready" });
addEventListener("message", async (e) => {
const type = e.data.type;
-
- if (!g.cache) {
- g.cache = await caches.open(version);
- }
- const cache = g.cache;
+ const cache = await caches.open(version);
switch (type) {
case "add-cache":
@@ -69,5 +71,13 @@ addEventListener("message", async (e) => {
await cache.add(e.data.url);
}
break;
+ case "define-route":
+ console.log("defining route", e.data.routes);
+ g.router = createRouter({ strictTrailingSlash: false });
+ for (const route of e.data.routes) {
+ g.router.insert(route.url, route);
+ }
+ await activate();
+ break;
}
});
diff --git a/app/web/src/utils/script/init-api.ts b/app/web/src/utils/script/init-api.ts
index ce448203..b5de9a05 100644
--- a/app/web/src/utils/script/init-api.ts
+++ b/app/web/src/utils/script/init-api.ts
@@ -130,16 +130,27 @@ export const reloadDBAPI = async (
await set(url, JSON.stringify(w.prasiApi[url]), cache);
};
+ const prasiBase = `${location.protocol}//${location.host}`;
try {
const found = await get(url, cache);
if (found) {
w.prasiApi[url] = JSON.parse(found);
- forceReload();
+ forceReload().catch(() => {
+ if (url === prasiBase) {
+ console.error("Failed to load prasi. Reloading...");
+ setTimeout(() => location.reload(), 3000);
+ }
+ });
} else {
await forceReload();
}
} catch (e) {
console.warn("Failed to load API");
+
+ if (url === prasiBase) {
+ console.error("Failed to load prasi. Reloading...");
+ setTimeout(() => location.reload(), 3000);
+ }
}
};
diff --git a/pkgs/core/index.ts b/pkgs/core/index.ts
index 9b9abcf9..579f1289 100644
--- a/pkgs/core/index.ts
+++ b/pkgs/core/index.ts
@@ -13,6 +13,7 @@ import { prepareApiRoutes } from "./server/api-scan";
g.status = "init";
await createLogger();
+g.api = {};
g.datadir = g.mode === "dev" ? ".data" : "../data";
g.port = parseInt(process.env.PORT || "4550");
g.mode = process.argv.includes("dev") ? "dev" : "prod";
@@ -31,7 +32,7 @@ if (g.db) {
});
}
-await createServer();
+createServer();
await parcelBuild();
await generateAPIFrm();
await prepareApiRoutes();
diff --git a/pkgs/core/server/api-scan.ts b/pkgs/core/server/api-scan.ts
index a8b78ade..8eacc046 100644
--- a/pkgs/core/server/api-scan.ts
+++ b/pkgs/core/server/api-scan.ts
@@ -22,7 +22,6 @@ export const prepareApiRoutes = async () => {
path: importPath.substring((root || path).length + 1),
};
g.api[filename] = route;
- g.router.insert(route.url.replace(/\*/gi, "**"), g.api[filename]);
} catch (e) {
g.log.warn(
`Failed to import app/srv/api${importPath.substring(
diff --git a/pkgs/core/server/create.ts b/pkgs/core/server/create.ts
index cbe05ea0..c9fea124 100644
--- a/pkgs/core/server/create.ts
+++ b/pkgs/core/server/create.ts
@@ -4,14 +4,25 @@ import { dir } from "../utils/dir";
import { g } from "../utils/global";
import { serveAPI } from "./serve-api";
import { WebSocketHandler } from "bun";
+import { waitUntil } from "web-utils/src/wait-until";
-const cache = { static: {} as Record };
+const cache = {
+ static: {} as Record<
+ string,
+ { type: string; content: ReadableStream }
+ >,
+};
export type WSData = { url: URL };
export const createServer = async () => {
- g.api = {};
+ await waitUntil(() => g.status !== "init");
g.router = createRouter({ strictTrailingSlash: false });
+
+ for (const route of Object.values(g.api)) {
+ g.router.insert(route.url.replace(/\*/gi, "**"), route);
+ }
+
g.server = Bun.serve({
port: g.port,
websocket: {
@@ -45,7 +56,6 @@ export const createServer = async () => {
},
} as WebSocketHandler,
async fetch(req, server) {
- if (g.status === "init") return new Response("initializing...");
const url = new URL(req.url);
if (wsHandler[url.pathname]) {
@@ -71,14 +81,20 @@ export const createServer = async () => {
}
try {
- if (cache.static[url.pathname]) {
- return new Response(cache.static[url.pathname]);
+ const found = cache.static[url.pathname];
+ if (found || g.mode === "prod") {
+ const res = new Response(found.content);
+ res.headers.set("Content-Type", found.type);
}
const file = Bun.file(dir.path(`app/static${url.pathname}`));
if ((await file.exists()) && file.type !== "application/octet-stream") {
- cache.static[url.pathname] = file;
- return new Response(file as any);
+ cache.static[url.pathname] = {
+ type: file.type,
+ content: file.stream(),
+ };
+ const found = cache.static[url.pathname];
+ return new Response(found.content);
}
} catch (e) {
g.log.error(e);