bzl

self-hosted ephemeral community engine
Log | Files | Refs | README | LICENSE

server.js (4024B)


      1 const fs = require("fs");
      2 const path = require("path");
      3 const http = require("http");
      4 const https = require("https");
      5 
      6 const CONFIG_PATH = path.join(__dirname, "config.json");
      7 
      8 function readJson(filePath, fallback) {
      9   try {
     10     return JSON.parse(fs.readFileSync(filePath, "utf8"));
     11   } catch {
     12     return fallback;
     13   }
     14 }
     15 
     16 function writeJson(filePath, value) {
     17   fs.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n", "utf8");
     18 }
     19 
     20 function normalizeUrl(s) {
     21   const raw = String(s || "").trim();
     22   if (!raw) return "";
     23   try {
     24     const u = new URL(raw);
     25     if (u.protocol !== "http:" && u.protocol !== "https:") return "";
     26     u.hash = "";
     27     return u.toString().replace(/\/+$/, "");
     28   } catch {
     29     return "";
     30   }
     31 }
     32 
     33 function postJson(targetUrl, token, payload) {
     34   return new Promise((resolve) => {
     35     let u;
     36     try {
     37       u = new URL(targetUrl);
     38     } catch {
     39       resolve({ ok: false, status: 0, error: "Invalid URL" });
     40       return;
     41     }
     42 
     43     const transport = u.protocol === "http:" ? http : https;
     44     const port = u.port ? Number(u.port) : u.protocol === "http:" ? 80 : 443;
     45     const body = Buffer.from(JSON.stringify(payload), "utf8");
     46     const req = transport.request(
     47       {
     48         method: "POST",
     49         hostname: u.hostname,
     50         port,
     51         path: u.pathname + u.search,
     52         headers: {
     53           "Content-Type": "application/json",
     54           "Content-Length": body.length,
     55           Authorization: token ? `Bearer ${token}` : ""
     56         }
     57       },
     58       (res) => {
     59         let out = "";
     60         res.on("data", (d) => (out += String(d || "")));
     61         res.on("end", () => resolve({ ok: res.statusCode >= 200 && res.statusCode < 300, status: res.statusCode || 0, body: out }));
     62       }
     63     );
     64     req.on("error", (e) => resolve({ ok: false, status: 0, error: e?.message || String(e) }));
     65     req.end(body);
     66   });
     67 }
     68 
     69 module.exports = function init(api) {
     70   const config = readJson(CONFIG_PATH, {
     71     directoryUrl: "",
     72     token: "",
     73     instance: { id: "", url: "", name: "", description: "", bzlVersion: "", requiresRegistrationCode: false },
     74     publicHives: []
     75   });
     76 
     77   api.registerWs("getConfig", (ws) => {
     78     if (ws?.user?.role !== "owner") return;
     79     api.sendToUsers([ws.user.username], { type: "plugin:directory-publisher:config", config });
     80   });
     81 
     82   api.registerWs("setConfig", (ws, msg) => {
     83     if (ws?.user?.role !== "owner") return;
     84     const next = msg?.config || {};
     85     config.directoryUrl = normalizeUrl(next.directoryUrl || config.directoryUrl);
     86     config.token = String(next.token || config.token || "").trim();
     87     config.instance = { ...(config.instance || {}), ...(next.instance || {}) };
     88     config.instance.url = normalizeUrl(config.instance.url);
     89     config.instance.id = String(config.instance.id || "").trim();
     90     config.instance.name = String(config.instance.name || "").trim();
     91     config.instance.description = String(config.instance.description || "").trim();
     92     config.instance.bzlVersion = String(config.instance.bzlVersion || "").trim();
     93     config.instance.requiresRegistrationCode = Boolean(config.instance.requiresRegistrationCode);
     94     config.publicHives = Array.isArray(next.publicHives) ? next.publicHives : config.publicHives;
     95     writeJson(CONFIG_PATH, config);
     96     api.sendToUsers([ws.user.username], { type: "plugin:directory-publisher:configSaved" });
     97   });
     98 
     99   api.registerWs("publishNow", async (ws) => {
    100     if (ws?.user?.role !== "owner") return;
    101     const urlBase = normalizeUrl(config.directoryUrl);
    102     if (!urlBase) return api.sendToUsers([ws.user.username], { type: "plugin:directory-publisher:result", ok: false, error: "Missing directory URL." });
    103     const endpoint = `${urlBase}/api/plugins/directory-server/announce`;
    104     const payload = { instance: config.instance, publicHives: config.publicHives };
    105     const r = await postJson(endpoint, config.token, payload);
    106     api.sendToUsers([ws.user.username], { type: "plugin:directory-publisher:result", ...r });
    107   });
    108 
    109   api.log("info", "directory-publisher loaded");
    110 };