const debug = require("debug")("action-dashboard:webhooks"); const { Webhooks, createNodeMiddleware } = require("@octokit/webhooks"); const http = require("http"); class WebHooks { constructor( sitePort, secret, webhookPort, webhookPath, gitHub, actions, expressApp ) { if (secret) { this._secret = secret; this._webhookPort = webhookPort; this._sitePort = sitePort; this._gitHub = gitHub; this._actions = actions; if (sitePort === webhookPort) { this._defaultPath = "/webhook"; } else { this._defaultPath = "/"; } this._path = webhookPath || this._defaultPath; // Fail in the case that the ports for the main site and webhooks // are the same and the path is explicitly set to / if (sitePort === webhookPort && this._path === "/") { throw new Error( "Path cannot be / when the webhooks are running on the same port as the main site" ); } this._expressApp = expressApp; this._enabled = true; } } start() { if (this._enabled) { debug( `Setting up webhooks port: ${this._webhookPort}, path: ${this._path}` ); // OctoKit webhooks, not this module const webhooks = new Webhooks({ secret: this._secret, }); webhooks.onError((error) => { console.dir(error); // console.error( // `Webhook error occured in "${error.event.name} handler: ${error.stack}"` // ); }); const middleware = createNodeMiddleware(webhooks, { path: this._path }); webhooks.on("workflow_run", this.workflowRun); if (this._sitePort !== this._webhookPort) { this._server = http .createServer((req, res) => { debug(`received request path: ${req.url}`); if (req.url === "/ping") { debug("ping"); res.statusCode = 200; res.end(); } else { middleware(req, res); } }) .listen({ port: this._webhookPort }, () => { console.log( `Listening for webhooks on ${this._webhookPort} at ${this._path}` ); }); } else { this._expressApp.use(this._path, middleware); console.log( `Listening for webhooks on ${this._webhookPort} at ${this._path}` ); } } else { debug("Webhooks disabled"); } } // Mainly used by testing functions to cleanly shutdown web server stop() { if (this._enabled && this._server && this._server.listening) { this._server.close(); } } workflowRun = async ({ id, name, payload }) => { try { debug(`workflow_run received id: ${id}, name: ${name}`, payload); let usage = null; if (payload.workflow_run.status === "completed") { debug(`getting usage for id: ${id}, name: ${name}`); usage = await this._gitHub.getUsage( payload.workflow_run.repository.owner.login, payload.workflow_run.repository.name, payload.workflow_run.workflow_id, payload.workflow_run.id ); } debug(`merging runs for id: ${id}, name: ${name}`); this._actions.mergeRuns([ { runId: payload.workflow_run.id, repo: payload.workflow_run.repository.name, owner: payload.workflow_run.repository.owner.login, workflowId: payload.workflow_run.workflow_id, runNumber: payload.workflow_run.run_number, workflow: payload.workflow_run.name, branch: payload.workflow_run.head_branch, sha: payload.workflow_run.head_sha, message: payload.workflow_run.head_commit.message, committer: payload.workflow_run.head_commit.committer.name, status: payload.workflow_run.status === "completed" ? payload.workflow_run.conclusion : payload.workflow_run.status, createdAt: payload.workflow_run.created_at, updatedAt: payload.workflow_run.updated_at, durationMs: usage?.run_duration_ms, }, ]); debug(`runs merged for id: ${id}, name: ${name}`); } catch (e) { console.dir(e); console.error( `Error processing workflow_run received id: ${id}, name: ${name}`, payload ); } }; } module.exports = WebHooks;