diff --git a/CHANGELOG b/CHANGELOG index f718ed9..f97b452 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,8 @@ +# 1.7.0 + +- Add $broadcast and $join +- Rework sessionId + # 1.6.9 - Add support for local envvars defined in [group]/env.kv in the format "KEY=value\n" diff --git a/dist/windows.zip b/dist/windows.zip index d1f737b..fd4f3b8 100644 Binary files a/dist/windows.zip and b/dist/windows.zip differ diff --git a/executors.js b/executors.js index 88d2e0a..2b2bd94 100644 --- a/executors.js +++ b/executors.js @@ -30,6 +30,11 @@ function clone(struct, name) { let orgFile = { name }; fs_1.default.writeFileSync(`${name}/.merrymake/conf.json`, JSON.stringify(orgFile)); yield (0, utils_1.execPromise)(`git clone --branch main -q "${config_1.GIT_HOST}/${name}/event-catalogue" event-catalogue`, name); + let dir = `${name}/public`; + fs_1.default.mkdirSync(dir, { recursive: true }); + yield (0, utils_1.execPromise)(`git init --initial-branch=main`, dir); + yield (0, utils_1.execPromise)(`git remote add origin "${config_1.GIT_HOST}/${name}/public"`, dir); + // await execPromise(`git fetch`, dir); fetch(".", name, struct); } catch (e) { diff --git a/executors.ts b/executors.ts index 104355f..9c1c2c6 100644 --- a/executors.ts +++ b/executors.ts @@ -34,6 +34,14 @@ async function clone(struct: any, name: string) { `git clone --branch main -q "${GIT_HOST}/${name}/event-catalogue" event-catalogue`, name ); + let dir = `${name}/public`; + fs.mkdirSync(dir, { recursive: true }); + await execPromise(`git init --initial-branch=main`, dir); + await execPromise( + `git remote add origin "${GIT_HOST}/${name}/public"`, + dir + ); + // await execPromise(`git fetch`, dir); fetch(".", name, struct); } catch (e) { throw e; diff --git a/package-lock.json b/package-lock.json index 27a5ebc..b2013fa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,29 +1,27 @@ { "name": "@merrymake/cli", - "version": "1.6.8", + "version": "1.6.9", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@merrymake/cli", - "version": "1.6.8", + "version": "1.6.9", "license": "ISC", "dependencies": { "@merrymake/detect-project-type": "^1.2.0", "@merrymake/ext2mime": "^1.0.0", - "express": "^4.17.3", - "express-session": "^1.17.3", - "uuid": "^9.0.0" + "cookie-parser": "^1.4.6", + "express": "^4.17.3" }, "bin": { "mm": "mm.js", "mmk": "mmk.js" }, "devDependencies": { + "@types/cookie-parser": "^1.4.6", "@types/express": "^4.17.13", - "@types/express-session": "^1.17.10", "@types/node": "^16.7.4", - "@types/uuid": "^9.0.1", "pkg": "^5.8.1" } }, @@ -197,6 +195,15 @@ "@types/node": "*" } }, + "node_modules/@types/cookie-parser": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.6.tgz", + "integrity": "sha512-KoooCrD56qlLskXPLGUiJxOMnv5l/8m7cQD2OxJ73NPMhuSz9PmvwRD6EpjDyKBVrdJDdQ4bQK7JFNHnNmax0w==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/express": { "version": "4.17.20", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.20.tgz", @@ -221,15 +228,6 @@ "@types/send": "*" } }, - "node_modules/@types/express-session": { - "version": "1.17.10", - "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.17.10.tgz", - "integrity": "sha512-U32bC/s0ejXijw5MAzyaV4tuZopCh/K7fPoUDyNbsRXHvPSeymygYD1RFL99YOLhF5PNOkzswvOTRaVHdL1zMw==", - "dev": true, - "dependencies": { - "@types/express": "*" - } - }, "node_modules/@types/http-errors": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.3.tgz", @@ -281,12 +279,6 @@ "@types/node": "*" } }, - "node_modules/@types/uuid": { - "version": "9.0.6", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.6.tgz", - "integrity": "sha512-BT2Krtx4xaO6iwzwMFUYvWBWkV2pr37zD68Vmp1CDV196MzczBRxuEpD6Pr395HAgebC/co7hOphs53r8V7jew==", - "dev": true - }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -584,6 +576,26 @@ "node": ">= 0.6" } }, + "node_modules/cookie-parser": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz", + "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==", + "dependencies": { + "cookie": "0.4.1", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/cookie-parser/node_modules/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", @@ -778,32 +790,6 @@ "node": ">= 0.10.0" } }, - "node_modules/express-session": { - "version": "1.17.3", - "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.3.tgz", - "integrity": "sha512-4+otWXlShYlG1Ma+2Jnn+xgKUZTMJ5QD3YvfilX3AcocOAbIkVylSWEklzALe/+Pu4qV6TYBj5GwOBFfdKqLBw==", - "dependencies": { - "cookie": "0.4.2", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "~2.0.0", - "on-headers": "~1.0.2", - "parseurl": "~1.3.3", - "safe-buffer": "5.2.1", - "uid-safe": "~2.1.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/express-session/node_modules/cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/fast-glob": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", @@ -1480,14 +1466,6 @@ "node": ">= 0.8" } }, - "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -1695,14 +1673,6 @@ } ] }, - "node_modules/random-bytes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", - "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -2191,17 +2161,6 @@ "node": ">= 0.6" } }, - "node_modules/uid-safe": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", - "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", - "dependencies": { - "random-bytes": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", @@ -2233,18 +2192,6 @@ "node": ">= 0.4.0" } }, - "node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -2470,6 +2417,15 @@ "@types/node": "*" } }, + "@types/cookie-parser": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.6.tgz", + "integrity": "sha512-KoooCrD56qlLskXPLGUiJxOMnv5l/8m7cQD2OxJ73NPMhuSz9PmvwRD6EpjDyKBVrdJDdQ4bQK7JFNHnNmax0w==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, "@types/express": { "version": "4.17.20", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.20.tgz", @@ -2494,15 +2450,6 @@ "@types/send": "*" } }, - "@types/express-session": { - "version": "1.17.10", - "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.17.10.tgz", - "integrity": "sha512-U32bC/s0ejXijw5MAzyaV4tuZopCh/K7fPoUDyNbsRXHvPSeymygYD1RFL99YOLhF5PNOkzswvOTRaVHdL1zMw==", - "dev": true, - "requires": { - "@types/express": "*" - } - }, "@types/http-errors": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.3.tgz", @@ -2554,12 +2501,6 @@ "@types/node": "*" } }, - "@types/uuid": { - "version": "9.0.6", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.6.tgz", - "integrity": "sha512-BT2Krtx4xaO6iwzwMFUYvWBWkV2pr37zD68Vmp1CDV196MzczBRxuEpD6Pr395HAgebC/co7hOphs53r8V7jew==", - "dev": true - }, "accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -2770,6 +2711,22 @@ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" }, + "cookie-parser": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz", + "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==", + "requires": { + "cookie": "0.4.1", + "cookie-signature": "1.0.6" + }, + "dependencies": { + "cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==" + } + } + }, "cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", @@ -2924,28 +2881,6 @@ "vary": "~1.1.2" } }, - "express-session": { - "version": "1.17.3", - "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.3.tgz", - "integrity": "sha512-4+otWXlShYlG1Ma+2Jnn+xgKUZTMJ5QD3YvfilX3AcocOAbIkVylSWEklzALe/+Pu4qV6TYBj5GwOBFfdKqLBw==", - "requires": { - "cookie": "0.4.2", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "~2.0.0", - "on-headers": "~1.0.2", - "parseurl": "~1.3.3", - "safe-buffer": "5.2.1", - "uid-safe": "~2.1.5" - }, - "dependencies": { - "cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==" - } - } - }, "fast-glob": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", @@ -3421,11 +3356,6 @@ "ee-first": "1.1.1" } }, - "on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" - }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -3572,11 +3502,6 @@ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true }, - "random-bytes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", - "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==" - }, "range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -3931,14 +3856,6 @@ "mime-types": "~2.1.24" } }, - "uid-safe": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", - "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", - "requires": { - "random-bytes": "~1.0.0" - } - }, "universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", @@ -3961,11 +3878,6 @@ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" }, - "uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==" - }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/package.json b/package.json index 9680118..9a4cea6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@merrymake/cli", - "version": "1.6.9", + "version": "1.7.0", "description": "", "main": "index.js", "scripts": { @@ -25,15 +25,13 @@ "dependencies": { "@merrymake/detect-project-type": "^1.2.0", "@merrymake/ext2mime": "^1.0.0", - "express": "^4.17.3", - "express-session": "^1.17.3", - "uuid": "^9.0.0" + "cookie-parser": "^1.4.6", + "express": "^4.17.3" }, "devDependencies": { + "@types/cookie-parser": "^1.4.6", "@types/express": "^4.17.13", - "@types/express-session": "^1.17.10", "@types/node": "^16.7.4", - "@types/uuid": "^9.0.1", "pkg": "^5.8.1" } } diff --git a/simulator.js b/simulator.js index 475fc7a..a7aad81 100644 --- a/simulator.js +++ b/simulator.js @@ -20,7 +20,7 @@ const child_process_1 = require("child_process"); const detect_project_type_1 = require("@merrymake/detect-project-type"); const http_1 = __importDefault(require("http")); const prompt_1 = require("./prompt"); -const express_session_1 = __importDefault(require("express-session")); +const cookie_parser_1 = __importDefault(require("cookie-parser")); const MILLISECONDS = 1; const SECONDS = 1000 * MILLISECONDS; const MINUTES = 60 * SECONDS; @@ -29,19 +29,14 @@ const DEFAULT_TIMEOUT = 5 * MINUTES; class Run { constructor(port) { this.port = port; + const { pathToRoot } = (0, utils_1.fetchOrg)(); + this.pathToRoot = pathToRoot; } execute() { return new Promise((resolve) => { - const { pathToRoot } = (0, utils_1.fetchOrg)(); - let teams = (0, utils_1.directoryNames)(new utils_1.Path(pathToRoot), ["event-catalogue"]).map((x) => x.name); const app = (0, express_1.default)(); const server = http_1.default.createServer(app); - app.use((0, express_session_1.default)({ - secret: "something that is kept or meant to be kept unknown or unseen by others", - resave: false, - saveUninitialized: true, - cookie: { maxAge: 12 * HOURS }, - })); + const withSession = (0, cookie_parser_1.default)(); app.use((req, res, next) => { if (req.is("multipart/form-data") || req.is("application/x-www-form-urlencoded")) { @@ -51,14 +46,13 @@ class Run { express_1.default.raw({ type: "*/*", limit: "10mb" })(req, res, next); } }); - let hooks; app.post("/trace/:sessionId/:traceId/:event", (req, res) => __awaiter(this, void 0, void 0, function* () { try { let traceId = req.params.traceId; let sessionId = req.params.sessionId; let event = req.params.event; let payload = req.body; - this.runService(pathToRoot, this.port, event, payload, traceId, sessionId, hooks, req.headers["content-type"]); + this.runService(this.pathToRoot, this.port, event, payload, traceId, sessionId, this.hooks, req.headers["content-type"]); res.send("Done"); } catch (e) { @@ -68,16 +62,10 @@ class Run { throw e; } })); - app.get("/rapids/:event", (req, res) => __awaiter(this, void 0, void 0, function* () { + app.get("/rapids/:event", withSession, (req, res) => __awaiter(this, void 0, void 0, function* () { try { - let event = req.params.event; - hooks = new PublicHooks(pathToRoot); let payload = Buffer.from(JSON.stringify(req.query)); - processFolders(pathToRoot, null, teams, hooks); - loadLocalEnvvars(pathToRoot); - let traceId = "t" + Math.random(); - let sessionId = req.session.id; - let response = yield this.runWithReply(pathToRoot, this.port, res, event, payload, traceId, sessionId, hooks, req.headers["content-type"]); + yield this.processEvent(req, res, payload); } catch (e) { if (e.data !== undefined) @@ -86,20 +74,14 @@ class Run { throw e; } })); - app.all("/rapids/:event", (req, res) => __awaiter(this, void 0, void 0, function* () { + app.all("/rapids/:event", withSession, (req, res) => __awaiter(this, void 0, void 0, function* () { try { - let event = req.params.event; - hooks = new PublicHooks(pathToRoot); let payload = !Buffer.isBuffer(req.body) ? typeof req.body === "object" ? Buffer.from(JSON.stringify(req.body)) : Buffer.from(req.body) : req.body; - processFolders(pathToRoot, null, teams, hooks); - loadLocalEnvvars(pathToRoot); - let traceId = "t" + Math.random(); - let sessionId = req.session.id; - let response = yield this.runWithReply(pathToRoot, this.port, res, event, payload, traceId, sessionId, hooks, req.headers["content-type"]); + yield this.processEvent(req, res, payload); } catch (e) { if (e.data !== undefined) @@ -129,6 +111,50 @@ class Run { }); }); } + processEvent(req, res, payload) { + return __awaiter(this, void 0, void 0, function* () { + try { + let sessionId = req.cookies.sessionId; + if (!sessionId) { + sessionId = "s" + Math.random(); + res.cookie("sessionId", sessionId); + } + res.set("Access-Control-Allow-Origin", "*"); + let event = req.params.event; + this.hooks = new PublicHooks(this.pathToRoot); + let conf = this.hooks.getApiConfig(event); + let traceId = "t" + Math.random(); + pendingReplies[traceId] = { + resp: res, + channels: new Set(), + }; + if (conf !== undefined && conf.streaming === true) { + req.on("close", () => { + let rep = pendingReplies[traceId]; + rep.channels.forEach((c) => { + channels[c].delete(rep.resp); + if (channels[c].size === 0) { + delete channels[c]; + } + }); + }); + res.set("Content-Type", "text/event-stream"); + res.set("Cache-Control", "no-cache"); + res.set("Connection", "keep-alive"); + res.flushHeaders(); + } + let teams = (0, utils_1.directoryNames)(new utils_1.Path(this.pathToRoot), [ + "event-catalogue", + ]).map((x) => x.name); + processFolders(this.pathToRoot, null, teams, this.hooks); + loadLocalEnvvars(this.pathToRoot); + let response = yield this.runWithReply(this.pathToRoot, this.port, res, event, payload, traceId, sessionId, this.hooks, req.headers["content-type"]); + } + catch (e) { + throw e; + } + }); + } runService(pathToRoot, port, event, payload, traceId, sessionId, hooks, contentType) { var _a; if (event === "$reply") { @@ -138,6 +164,25 @@ class Run { reply(rs.resp, HTTP.SUCCESS.SINGLE_REPLY(payload), contentType); } } + else if (event === "$join") { + let to = payload.toString(); + let rs = pendingReplies[traceId]; + if (rs !== undefined) { + if (channels[to] === undefined) + channels[to] = new Set(); + channels[to].add(rs.resp); + rs.channels.add(to); + } + } + else if (event === "$broadcast") { + let p = JSON.parse(payload.toString()); + let cs = channels[p.to] || []; + cs.forEach((c) => { + c.write(`event: ${p.event}\n`); + p.payload.split("\n").forEach((x) => c.write(`data: ${x}\n`)); + c.write(`\n`); + }); + } let rivers = (_a = hooks.riversFor(event)) === null || _a === void 0 ? void 0 : _a.hooks; if (rivers === undefined) return; @@ -171,6 +216,16 @@ class Run { (": " + data).trimEnd() + Reset); }); + // ls.on("exit", () => { + // let streaming = pendingReplies[traceId].streaming; + // if (streaming !== undefined) { + // streaming.running--; + // if (streaming.running === 0) { + // pendingReplies[traceId].resp.end(); + // delete pendingReplies[traceId]; + // } + // } + // }); }); } runWithReply(pathToRoot, port, resp, event, payload, traceId, sessionId, hooks, contentType) { @@ -179,13 +234,15 @@ class Run { let rivers = hooks.riversFor(event); if (rivers === undefined) return reply(resp, HTTP.CLIENT_ERROR.NO_HOOKS, undefined); + let conf = hooks.getApiConfig(event); this.runService(pathToRoot, port, event, payload, traceId, sessionId, hooks, contentType); - pendingReplies[traceId] = { resp, replies: [] }; - yield sleep(rivers.waitFor || MAX_WAIT); - let pending = pendingReplies[traceId]; - if (pending !== undefined) { - delete pendingReplies[traceId]; - reply(resp, HTTP.SUCCESS.QUEUE_JOB, undefined); + if (conf !== undefined && conf.streaming !== true) { + yield sleep(conf.waitFor || MAX_WAIT); + let pending = pendingReplies[traceId]; + if (pending !== undefined) { + delete pendingReplies[traceId]; + reply(resp, HTTP.SUCCESS.QUEUE_JOB, undefined); + } } } catch (e) { @@ -200,11 +257,15 @@ const Reset = "\x1b[0m"; const FgRed = "\x1b[31m"; let envvars = {}; let pendingReplies = {}; +let channels = {}; class PublicHooks { constructor(pathToRoot) { this.hooks = {}; this.publicEvents = JSON.parse("" + fs_1.default.readFileSync(`${pathToRoot}/event-catalogue/api.json`)); } + getApiConfig(event) { + return this.publicEvents[event]; + } register(event, river, hook) { var _a; let evt = this.hooks[event] || diff --git a/simulator.ts b/simulator.ts index 5646974..5b1707f 100644 --- a/simulator.ts +++ b/simulator.ts @@ -1,5 +1,5 @@ import { output2, fetchOrg, directoryNames, Path } from "./utils"; -import express from "express"; +import express, { Request, RequestHandler } from "express"; import { Response } from "express"; import fs from "fs"; import { spawn, ExecOptions } from "child_process"; @@ -10,7 +10,7 @@ import { } from "@merrymake/detect-project-type"; import http from "http"; import { YELLOW, NORMAL_COLOR } from "./prompt"; -import session from "express-session"; +import cookieParser from "cookie-parser"; const MILLISECONDS = 1; const SECONDS = 1000 * MILLISECONDS; @@ -19,27 +19,17 @@ const HOURS = 60 * MINUTES; const DEFAULT_TIMEOUT = 5 * MINUTES; export class Run { - constructor(private port: number) {} + private hooks: PublicHooks | undefined; + private pathToRoot: string; + constructor(private port: number) { + const { pathToRoot } = fetchOrg(); + this.pathToRoot = pathToRoot; + } execute() { return new Promise((resolve) => { - const { pathToRoot } = fetchOrg(); - - let teams = directoryNames(new Path(pathToRoot), ["event-catalogue"]).map( - (x) => x.name - ); - const app = express(); const server = http.createServer(app); - - app.use( - session({ - secret: - "something that is kept or meant to be kept unknown or unseen by others", - resave: false, - saveUninitialized: true, - cookie: { maxAge: 12 * HOURS }, - }) - ); + const withSession: RequestHandler<{ event: string }> = cookieParser(); app.use((req, res, next) => { if ( @@ -52,8 +42,6 @@ export class Run { } }); - let hooks: PublicHooks; - app.post("/trace/:sessionId/:traceId/:event", async (req, res) => { try { let traceId = req.params.traceId; @@ -61,13 +49,13 @@ export class Run { let event = req.params.event; let payload: Buffer = req.body; this.runService( - pathToRoot, + this.pathToRoot, this.port, event, payload, traceId, sessionId, - hooks, + this.hooks!, req.headers["content-type"] ); res.send("Done"); @@ -77,56 +65,24 @@ export class Run { } }); - app.get("/rapids/:event", async (req, res) => { + app.get("/rapids/:event", withSession, async (req, res) => { try { - let event = req.params.event; - hooks = new PublicHooks(pathToRoot); let payload: Buffer = Buffer.from(JSON.stringify(req.query)); - processFolders(pathToRoot, null, teams, hooks); - loadLocalEnvvars(pathToRoot); - let traceId = "t" + Math.random(); - let sessionId = req.session.id; - let response = await this.runWithReply( - pathToRoot, - this.port, - res, - event, - payload, - traceId, - sessionId, - hooks, - req.headers["content-type"] - ); + await this.processEvent(req, res, payload); } catch (e: any) { if (e.data !== undefined) reply(res, e, undefined); else throw e; } }); - app.all("/rapids/:event", async (req, res) => { + app.all("/rapids/:event", withSession, async (req, res) => { try { - let event = req.params.event; - hooks = new PublicHooks(pathToRoot); let payload: Buffer = !Buffer.isBuffer(req.body) ? typeof req.body === "object" ? Buffer.from(JSON.stringify(req.body)) : Buffer.from(req.body) : req.body; - processFolders(pathToRoot, null, teams, hooks); - loadLocalEnvvars(pathToRoot); - let traceId = "t" + Math.random(); - let sessionId = req.session.id; - let response = await this.runWithReply( - pathToRoot, - this.port, - res, - event, - payload, - traceId, - sessionId, - hooks, - req.headers["content-type"] - ); + await this.processEvent(req, res, payload); } catch (e: any) { if (e.data !== undefined) reply(res, e, undefined); else throw e; @@ -176,6 +132,62 @@ export class Run { }); } + async processEvent( + req: Request<{ event: string }, any, any, unknown, Record>, + res: Response, + payload: Buffer + ) { + try { + let sessionId = req.cookies.sessionId; + if (!sessionId) { + sessionId = "s" + Math.random(); + res.cookie("sessionId", sessionId); + } + res.set("Access-Control-Allow-Origin", "*"); + let event = req.params.event; + this.hooks = new PublicHooks(this.pathToRoot); + let conf = this.hooks.getApiConfig(event); + let traceId = "t" + Math.random(); + pendingReplies[traceId] = { + resp: res, + channels: new Set(), + }; + if (conf !== undefined && conf.streaming === true) { + req.on("close", () => { + let rep = pendingReplies[traceId]; + rep.channels.forEach((c) => { + channels[c].delete(rep.resp); + if (channels[c].size === 0) { + delete channels[c]; + } + }); + }); + res.set("Content-Type", "text/event-stream"); + res.set("Cache-Control", "no-cache"); + res.set("Connection", "keep-alive"); + res.flushHeaders(); + } + let teams = directoryNames(new Path(this.pathToRoot), [ + "event-catalogue", + ]).map((x) => x.name); + processFolders(this.pathToRoot, null, teams, this.hooks); + loadLocalEnvvars(this.pathToRoot); + let response = await this.runWithReply( + this.pathToRoot, + this.port, + res, + event, + payload, + traceId, + sessionId, + this.hooks, + req.headers["content-type"] + ); + } catch (e) { + throw e; + } + } + runService( pathToRoot: string, port: number, @@ -192,6 +204,24 @@ export class Run { delete pendingReplies[traceId]; reply(rs.resp, HTTP.SUCCESS.SINGLE_REPLY(payload), contentType); } + } else if (event === "$join") { + let to = payload.toString(); + let rs = pendingReplies[traceId]; + if (rs !== undefined) { + if (channels[to] === undefined) channels[to] = new Set(); + channels[to].add(rs.resp); + rs.channels.add(to); + } + } else if (event === "$broadcast") { + let p: { event: string; to: string; payload: string } = JSON.parse( + payload.toString() + ); + let cs = channels[p.to] || []; + cs.forEach((c) => { + c.write(`event: ${p.event}\n`); + p.payload.split("\n").forEach((x) => c.write(`data: ${x}\n`)); + c.write(`\n`); + }); } let rivers = hooks.riversFor(event)?.hooks; if (rivers === undefined) return; @@ -232,6 +262,16 @@ export class Run { Reset ); }); + // ls.on("exit", () => { + // let streaming = pendingReplies[traceId].streaming; + // if (streaming !== undefined) { + // streaming.running--; + // if (streaming.running === 0) { + // pendingReplies[traceId].resp.end(); + // delete pendingReplies[traceId]; + // } + // } + // }); }); } @@ -250,6 +290,7 @@ export class Run { let rivers = hooks.riversFor(event); if (rivers === undefined) return reply(resp, HTTP.CLIENT_ERROR.NO_HOOKS, undefined); + let conf = hooks.getApiConfig(event); this.runService( pathToRoot, port, @@ -260,13 +301,13 @@ export class Run { hooks, contentType ); - - pendingReplies[traceId] = { resp, replies: [] }; - await sleep(rivers.waitFor || MAX_WAIT); - let pending = pendingReplies[traceId]; - if (pending !== undefined) { - delete pendingReplies[traceId]; - reply(resp, HTTP.SUCCESS.QUEUE_JOB, undefined); + if (conf !== undefined && conf.streaming !== true) { + await sleep(conf.waitFor || MAX_WAIT); + let pending = pendingReplies[traceId]; + if (pending !== undefined) { + delete pendingReplies[traceId]; + reply(resp, HTTP.SUCCESS.QUEUE_JOB, undefined); + } } } catch (e) { throw e; @@ -287,21 +328,20 @@ interface Hook { action: string; } let pendingReplies: { - [traceId: string]: { resp: Response; count?: number; replies: string[] }; + [traceId: string]: { resp: Response; channels: Set }; } = {}; +let channels: { [channel: string]: Set } = {}; class PublicHooks { private publicEvents: { [event: string]: { - waitFor: number | undefined; - fileCount: number | undefined; - fileSize: number | undefined; - mimeTypes: string[] | undefined; + waitFor?: number; + streaming?: boolean; }; }; private hooks: { [event: string]: { - waitFor: number | undefined; + waitFor?: number; hooks: { [river: string]: Hook[] }; }; } = {}; @@ -311,6 +351,10 @@ class PublicHooks { ); } + getApiConfig(event: string) { + return this.publicEvents[event]; + } + register(event: string, river: string, hook: Hook) { let evt = this.hooks[event] ||