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:
parent
cda04f48ed
commit
324f4abd4b
1 changed files with 90 additions and 34 deletions
124
index.js
124
index.js
|
|
@ -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(
|
||||||
headInjection: struct.head_injection,
|
renderTemplate(req.params.structure_id, template.name, context.it),
|
||||||
}),
|
{
|
||||||
|
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) {
|
||||||
|
console.log(e);
|
||||||
|
res.status(500);
|
||||||
|
return res.send(e);
|
||||||
}
|
}
|
||||||
catch (e) {
|
|
||||||
console.log(e)
|
|
||||||
res.status(500);
|
|
||||||
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) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue