hook-engine
Version:
Production-grade webhook engine with comprehensive adapter support, security, reliability, structured logging, and CLI tools.
237 lines (236 loc) • 8.85 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const crypto_1 = __importDefault(require("crypto"));
/**
* GitHub webhook adapter
* Supports: push, pull_request, issues, releases, workflow_run, and more
* Documentation: https://docs.github.com/en/developers/webhooks-and-events/webhooks
*/
const github = {
getSignature(req) {
// GitHub sends signature in X-Hub-Signature-256 header (preferred) or X-Hub-Signature (legacy)
return req.headers["x-hub-signature-256"] || req.headers["x-hub-signature"];
},
verifySignature(rawBody, sigHeader, secret) {
if (!sigHeader)
return false;
try {
let signature;
let algorithm;
if (sigHeader.startsWith('sha256=')) {
// Modern GitHub signature format
algorithm = 'sha256';
signature = sigHeader.slice(7);
}
else if (sigHeader.startsWith('sha1=')) {
// Legacy GitHub signature format
algorithm = 'sha1';
signature = sigHeader.slice(5);
}
else {
return false;
}
const expected = crypto_1.default
.createHmac(algorithm, secret)
.update(rawBody)
.digest("hex");
return crypto_1.default.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}
catch (error) {
console.error('GitHub signature verification error:', error);
return false;
}
},
parsePayload(body) {
return JSON.parse(body.toString("utf8"));
},
normalize(event, options) {
// Extract common fields
const action = event.action || 'unknown';
const eventType = event.zen ? 'ping' : (event.hook_id ? 'ping' : 'unknown');
// Determine event type from webhook event structure
let type = 'unknown';
let id = '';
let timestamp = Math.floor(Date.now() / 1000);
let payload = {};
// Handle different GitHub event types
if (event.zen) {
// Ping event
type = 'ping';
id = `ping_${event.hook_id || Date.now()}`;
payload = { zen: event.zen, hook_id: event.hook_id };
}
else if (event.commits) {
// Push event
type = 'push';
id = event.head_commit?.id || event.after || `push_${Date.now()}`;
timestamp = event.head_commit?.timestamp ?
Math.floor(new Date(event.head_commit.timestamp).getTime() / 1000) :
timestamp;
payload = {
ref: event.ref,
before: event.before,
after: event.after,
commits: event.commits,
head_commit: event.head_commit,
repository: event.repository,
pusher: event.pusher,
sender: event.sender,
forced: event.forced,
created: event.created,
deleted: event.deleted
};
}
else if (event.pull_request) {
// Pull request event
type = `pull_request.${action}`;
id = `pr_${event.pull_request.id}_${action}`;
timestamp = Math.floor(new Date(event.pull_request.updated_at).getTime() / 1000);
payload = {
action,
number: event.pull_request.number,
pull_request: event.pull_request,
repository: event.repository,
sender: event.sender,
changes: event.changes
};
}
else if (event.issue) {
// Issue event
type = `issues.${action}`;
id = `issue_${event.issue.id}_${action}`;
timestamp = Math.floor(new Date(event.issue.updated_at).getTime() / 1000);
payload = {
action,
issue: event.issue,
repository: event.repository,
sender: event.sender,
assignee: event.assignee,
label: event.label,
changes: event.changes
};
}
else if (event.release) {
// Release event
type = `release.${action}`;
id = `release_${event.release.id}_${action}`;
timestamp = Math.floor(new Date(event.release.published_at || event.release.created_at).getTime() / 1000);
payload = {
action,
release: event.release,
repository: event.repository,
sender: event.sender
};
}
else if (event.workflow_run) {
// GitHub Actions workflow run
type = `workflow_run.${action}`;
id = `workflow_${event.workflow_run.id}_${action}`;
timestamp = Math.floor(new Date(event.workflow_run.updated_at).getTime() / 1000);
payload = {
action,
workflow_run: event.workflow_run,
repository: event.repository,
sender: event.sender
};
}
else if (event.deployment) {
// Deployment event
type = `deployment.${action || 'created'}`;
id = `deployment_${event.deployment.id}`;
timestamp = Math.floor(new Date(event.deployment.updated_at).getTime() / 1000);
payload = {
action: action || 'created',
deployment: event.deployment,
repository: event.repository,
sender: event.sender
};
}
else if (event.check_run) {
// Check run event
type = `check_run.${action}`;
id = `check_run_${event.check_run.id}_${action}`;
timestamp = Math.floor(new Date(event.check_run.completed_at || event.check_run.started_at).getTime() / 1000);
payload = {
action,
check_run: event.check_run,
repository: event.repository,
sender: event.sender
};
}
else if (event.check_suite) {
// Check suite event
type = `check_suite.${action}`;
id = `check_suite_${event.check_suite.id}_${action}`;
timestamp = Math.floor(new Date(event.check_suite.updated_at).getTime() / 1000);
payload = {
action,
check_suite: event.check_suite,
repository: event.repository,
sender: event.sender
};
}
else if (event.star) {
// Star event
type = `star.${action}`;
id = `star_${event.repository.id}_${event.sender.id}_${action}`;
payload = {
action,
starred_at: event.starred_at,
repository: event.repository,
sender: event.sender
};
}
else if (event.fork) {
// Fork event
type = 'fork';
id = `fork_${event.forkee.id}`;
timestamp = Math.floor(new Date(event.forkee.created_at).getTime() / 1000);
payload = {
forkee: event.forkee,
repository: event.repository,
sender: event.sender
};
}
else if (event.member) {
// Member event (collaborator added/removed)
type = `member.${action}`;
id = `member_${event.member.id}_${action}`;
payload = {
action,
member: event.member,
repository: event.repository,
sender: event.sender
};
}
else if (event.organization) {
// Organization event
type = `organization.${action}`;
id = `org_${event.organization.id}_${action}`;
payload = {
action,
organization: event.organization,
sender: event.sender,
membership: event.membership
};
}
else {
// Generic fallback for unknown events
type = action ? `unknown.${action}` : 'unknown';
id = `github_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
payload = event;
}
return {
id,
type,
source: "github",
timestamp,
payload,
raw: options?.includeRaw ? JSON.stringify(event) : '',
};
},
};
exports.default = github;