import { createHash, createHmac } from 'node:crypto'; import { gzipSync } from 'node:zlib'; export const TEST_SECRET = 'test-hmac-secret-0123456789abcdef0123'; export function signature(secret: string, ts: number, body: Buffer): string { const bodyHash = createHash('sha256').update(body).digest('hex'); return createHmac('sha256', secret).update(`${ts}.${bodyHash}`).digest('hex'); } export interface SignOpts { token: string; secret?: string; ts?: number; gzip?: boolean; serverId?: string; } /** Builds headers + payload exactly as the mod sends them: signature covers the transmitted (gzipped) bytes. */ export function signedRequest(body: unknown, opts: SignOpts): { headers: Record; payload: Buffer } { const raw = Buffer.from(typeof body === 'string' ? body : JSON.stringify(body)); const gzip = opts.gzip ?? true; const payload = gzip ? gzipSync(raw) : raw; const ts = opts.ts ?? Date.now(); const headers: Record = { authorization: `Bearer ${opts.token}`, 'content-type': 'application/json', 'x-atm-server': opts.serverId ?? 'atm10', 'x-atm-timestamp': String(ts), 'x-atm-signature': signature(opts.secret ?? TEST_SECRET, ts, payload), }; if (gzip) headers['content-encoding'] = 'gzip'; return { headers, payload }; }