this was suppsoed to just be the fuckin uhhhh encoding fix for notifs but its bigger than that so idk

This commit is contained in:
Your Name 2026-08-07 17:46:01 -04:00
parent cda04f48ed
commit 324f4abd4b

118
index.js
View file

@ -2,7 +2,7 @@ const fs = require("fs");
const util = require("util"); const util = require("util");
const vm = require("node:vm"); const vm = require("node:vm");
const path = require("path"); const path = require("path");
require('dotenv').config() require("dotenv").config();
const express = require("express"); const express = require("express");
const session = require("express-session"); const session = require("express-session");
const fileUpload = require("express-fileupload"); const fileUpload = require("express-fileupload");
@ -19,7 +19,7 @@ const model = require("./db");
const PORT = process.env.PORT || 3000; const PORT = process.env.PORT || 3000;
const db = model.db; const db = model.db;
const wsRouter = express.Router() const wsRouter = express.Router();
let viewpath = path.join(__dirname, "views"); let viewpath = path.join(__dirname, "views");
let eta = new Eta({ views: viewpath, cache: false, autoEscape: true }); let eta = new Eta({ views: viewpath, cache: false, autoEscape: true });
@ -36,7 +36,12 @@ let routeIndex = Object.freeze({
}); });
const wsConnections = new Map(); const wsConnections = new Map();
const INSPECT_OPTS = { showHidden: false, depth: null, colors: false, compact: false }; const INSPECT_OPTS = {
showHidden: false,
depth: null,
colors: false,
compact: false,
};
function inspectArgs(args) { function inspectArgs(args) {
return args.map((v) => util.inspect(v, INSPECT_OPTS)).join(" "); return args.map((v) => util.inspect(v, INSPECT_OPTS)).join(" ");
} }
@ -67,8 +72,8 @@ app.use(
app.use("/", wsRouter); app.use("/", wsRouter);
app.use("/plumbing", require("./plumbing")); app.use("/plumbing", require("./plumbing"));
const vapidPublicKey = process.env.VAPID_PUBLIC_KEY const vapidPublicKey = process.env.VAPID_PUBLIC_KEY;
const vapidPrivateKey = process.env.VAPID_PRIVATE_KEY const vapidPrivateKey = process.env.VAPID_PRIVATE_KEY;
// Configure web-push with your VAPID details // Configure web-push with your VAPID details
webPush.setVapidDetails( webPush.setVapidDetails(
@ -77,7 +82,37 @@ webPush.setVapidDetails(
vapidPrivateKey, vapidPrivateKey,
); );
async function saveFile(structureId, req, uploadedFile, asset=false) { // KaiOS's push service only understands the legacy draft `aesgcm` content
// encoding; everyone else (iOS, Chrome, Firefox, modern Safari) uses the
// RFC 8291 standard `aes128gcm`, which is web-push's default. The subscription
// endpoint hostname tells us which is which, so we pick per-subscription and
// keep the standard as the default for anything we don't recognize.
const LEGACY_AESGCM_HOSTS = ["push.kaiostech.com", "kai.jiophone.net"];
function pushEncodingFor(subscription) {
try {
const host = new URL(subscription.endpoint).hostname;
return LEGACY_AESGCM_HOSTS.some((h) => host === h || host.endsWith("." + h))
? "aesgcm"
: "aes128gcm";
} catch {
return "aes128gcm";
}
}
// Transparent drop-in for the web-push module handed to structures via
// require('push'): identical API (setVapidDetails, generateVAPIDKeys, …) via
// the prototype chain, but sendNotification auto-selects the content encoding
// from the subscription endpoint unless the caller passed one explicitly.
const push = Object.create(webPush);
push.sendNotification = function (subscription, payload, options = {}) {
return webPush.sendNotification(subscription, payload, {
...options,
contentEncoding: options.contentEncoding || pushEncodingFor(subscription),
});
};
async function saveFile(structureId, req, uploadedFile, asset = false) {
const name = uploadedFile.name; const name = uploadedFile.name;
const [mime_type, mime_subtype] = uploadedFile.mimetype.split("/"); const [mime_type, mime_subtype] = uploadedFile.mimetype.split("/");
// structureId may arrive as a number (route.structure_id is an INTEGER) when // structureId may arrive as a number (route.structure_id is an INTEGER) when
@ -103,7 +138,7 @@ async function saveFile(structureId, req, uploadedFile, asset=false) {
let file = model.getFile(id); let file = model.getFile(id);
file.url = prefixUrlWithHost(req, file.path); file.url = prefixUrlWithHost(req, file.path);
return file return file;
} }
// A console whose log() mirrors to stdout and to the structure's logs table. // A console whose log() mirrors to stdout and to the structure's logs table.
@ -120,7 +155,12 @@ function makeConsole(structureId, routeId) {
// returning its exports. Each library runs in its own context so nothing // returning its exports. Each library runs in its own context so nothing
// leaks between dbs. // leaks between dbs.
function runLibrary(sql, librarySource, console) { function runLibrary(sql, librarySource, console) {
const libContext = vm.createContext({ sql, console, fetch, module: { exports: null } }); const libContext = vm.createContext({
sql,
console,
fetch,
module: { exports: null },
});
vm.runInContext(librarySource, libContext); vm.runInContext(librarySource, libContext);
return libContext.module.exports; return libContext.module.exports;
} }
@ -130,12 +170,15 @@ function makeLibs(structureId, console) {
const dbs = {}; const dbs = {};
for (let appDb of model.getDbsForStructure(structureId)) { for (let appDb of model.getDbsForStructure(structureId)) {
const sql = model.getDbInstance(appDb.id); const sql = model.getDbInstance(appDb.id);
dbs[appDb.alias] = { library: runLibrary(sql, appDb.library, console), sql }; dbs[appDb.alias] = {
library: runLibrary(sql, appDb.library, console),
sql,
};
} }
return { return {
eta: model.getTemplater(structureId), eta: model.getTemplater(structureId),
db: (alias) => dbs[alias], db: (alias) => dbs[alias],
push: webPush, push: push,
files: { saveFile: (...args) => saveFile(structureId, ...args) }, files: { saveFile: (...args) => saveFile(structureId, ...args) },
}; };
} }
@ -180,7 +223,6 @@ function routeWithPrefix(route) {
return withPrefix(route.route_prefix, route.path); return withPrefix(route.route_prefix, route.path);
} }
function compileWebsocketHandler(route) { function compileWebsocketHandler(route) {
try { try {
// Keep the existing WS handler context for compatibility. Restricting the // Keep the existing WS handler context for compatibility. Restricting the
@ -482,8 +524,7 @@ function fragmentFromPage(html) {
// carrying provenance becomes a decorated fragment; anything else passes through. // carrying provenance becomes a decorated fragment; anything else passes through.
function decorate(html, { headInjection, source, fragment } = {}) { function decorate(html, { headInjection, source, fragment } = {}) {
const lower = html.toLowerCase(); const lower = html.toLowerCase();
const isFullDoc = const isFullDoc = lower.startsWith("<html>") || lower.startsWith("<!doctype");
lower.startsWith("<html>") || lower.startsWith("<!doctype");
if (!fragment) { if (!fragment) {
return decoratePage(html, { headInjection, source }); return decoratePage(html, { headInjection, source });
} }
@ -518,10 +559,13 @@ app.post("/_bliss/render-template", (req, res) => {
req.body?.contextId, req.body?.contextId,
templateId, templateId,
); );
if (!savedContext) return res.status(410).send("Template context expired; refresh the page"); if (!savedContext)
return res.status(410).send("Template context expired; refresh the page");
const context = { ...savedContext }; const context = { ...savedContext };
context.route = makeRoute(structure); context.route = makeRoute(structure);
return res.type("html").send(renderTemplate(structureId, template.name, context)); return res
.type("html")
.send(renderTemplate(structureId, template.name, context));
}); });
// Provenance for a rendered response: GET routes are re-embeddable, so they // Provenance for a rendered response: GET routes are re-embeddable, so they
@ -578,7 +622,11 @@ function sidebarStuff(structId) {
} }
app.get("/workshop/:structure_id", (req, res) => { app.get("/workshop/:structure_id", (req, res) => {
return renderWorkshop(res, "workshop/editor", sidebarStuff(req.params.structure_id)); return renderWorkshop(
res,
"workshop/editor",
sidebarStuff(req.params.structure_id),
);
}); });
app.post("/workshop/:structure_id/clone", (req, res) => { app.post("/workshop/:structure_id/clone", (req, res) => {
@ -663,8 +711,11 @@ app.put("/workshop/:structure_id/route/:route_id", (req, res) => {
model.updateRoute({ ...route, ...req.body }); model.updateRoute({ ...route, ...req.body });
if (req.body.scaffold_page) { if (req.body.scaffold_page) {
// TODO: make this send over scaffold page id too someday // TODO: make this send over scaffold page id too someday
const latestPage = model.getLatestScaffoldPage(req.params.route_id) const latestPage = model.getLatestScaffoldPage(req.params.route_id);
model.updateScaffoldPage({ ...latestPage, content: req.body.scaffold_page }) model.updateScaffoldPage({
...latestPage,
content: req.body.scaffold_page,
});
} }
buildRoutes(); buildRoutes();
return res.send("good"); return res.send("good");
@ -773,9 +824,12 @@ app.get("/workshop/:structure_id/template/:template_id/preview", (req, res) => {
context.it.route = makeRoute(struct); context.it.route = makeRoute(struct);
return res.send( return res.send(
decorate(renderTemplate(req.params.structure_id, template.name, context.it), { decorate(
renderTemplate(req.params.structure_id, template.name, context.it),
{
headInjection: struct.head_injection, headInjection: struct.head_injection,
}), },
),
); );
}); });
@ -784,16 +838,16 @@ app.get("/workshop/:structure_id/route/:id", (req, res) => {
let routePrefix = model.getStructure(req.params.structure_id).route_prefix; let routePrefix = model.getStructure(req.params.structure_id).route_prefix;
let previewUrl = null; let previewUrl = null;
if (route["verb"] == "GET" ) { if (route["verb"] == "GET") {
previewUrl = routePrefix previewUrl = routePrefix
? path.join(routePrefix || "", route.path) ? path.join(routePrefix || "", route.path)
: route.path; : route.path;
} } else {
else {
previewUrl = `/workshop/${req.params.structure_id}/route/${req.params.id}/preview`; previewUrl = `/workshop/${req.params.structure_id}/route/${req.params.id}/preview`;
} }
const template = route["verb"] == "GET" ? "workshop/route" : "workshop/cud_route"; const template =
route["verb"] == "GET" ? "workshop/route" : "workshop/cud_route";
return renderWorkshop(res, template, { return renderWorkshop(res, template, {
route: route, route: route,
@ -844,15 +898,13 @@ app.post("/workshop/:structure_id/files", async (req, res) => {
const targetFile = req.files.file; const targetFile = req.files.file;
try { try {
const uploadedFile = await saveFile(structure_id, req, targetFile) const uploadedFile = await saveFile(structure_id, req, targetFile);
return res.send(eta.render("workshop/file_detail", { file: uploadedFile })); return res.send(eta.render("workshop/file_detail", { file: uploadedFile }));
} } catch (e) {
catch (e) { console.log(e);
console.log(e)
res.status(500); res.status(500);
return res.send(e); return res.send(e);
} }
}); });
function prefixUrlWithHost(req, path) { function prefixUrlWithHost(req, path) {
@ -973,9 +1025,13 @@ app.all("*", async (req, res) => {
}), }),
); );
}; };
const handlerScript = new vm.Script(route.handler, { filename: `handler_${route.id}.js` }); const handlerScript = new vm.Script(route.handler, {
filename: `handler_${route.id}.js`,
});
// Prepare the execution code that invokes `handler(req, res)` and awaits it // Prepare the execution code that invokes `handler(req, res)` and awaits it
const executionScript = new vm.Script(`handler(req, res); `, { filename: `execution_${route.id}.js` }); const executionScript = new vm.Script(`handler(req, res); `, {
filename: `execution_${route.id}.js`,
});
await handlerScript.runInContext(context); await handlerScript.runInContext(context);
const result = await executionScript.runInContext(context); const result = await executionScript.runInContext(context);
} catch (e) { } catch (e) {