/** * Payload Builder - Assembling the Final Form * * Takes all our crafted components and stitches them into * the exact structure hCaptcha expects. * * From h.html source, the payload object (s) contains: * {v, sitekey, host, hl, motionData, n, c, rqdata, pst, pd, pdc, pem...} * * IMPORTANT: The 'c' field is included in the payload object but gets * REMOVED before encryption. The encrypted body is then packed as: * msgpack.encode([JSON.stringify(c), encrypted_payload_without_c]) */ export class PayloadBuilder { /** * Build the getcaptcha request payload * * The returned object includes 'c' - the caller is responsible for * cloning and deleting 'c' before encryption (matching h.html behavior). */ static build({ siteKey, host, n, c, motionData, rqdata = '' }) { const now = Date.now(); return { v: '1', // Protocol version sitekey: siteKey, host, hl: 'en', // Language // Challenge response n, // Proof of work from hsw.js // c field — will be stripped before encryption, // then used separately in body packing as msgpack([c_string, encrypted]) c: typeof c === 'string' ? c : JSON.stringify(c), // Motion telemetry motionData: typeof motionData === 'string' ? motionData : JSON.stringify(motionData), // Additional fields from h.html source rqdata, // Request data from checksiteconfig pst: false, // Previous success token // Performance / detection data pd: JSON.stringify({ si: now - 5000, // Script init ce: now - 4500, // Challenge end cs: now - 4000, // Challenge start re: now - 500, // Response end rs: now - 1000, // Response start }), pdc: JSON.stringify({}), // Performance data cached pem: JSON.stringify({}), // Performance event map // Previous state prev: JSON.stringify({ escaped: false, passed: false, expiredChallenge: false, expiredResponse: false, }), }; } /** * Build form-encoded payload (alternative format for non-encrypted requests) */ static buildFormData(data) { const params = new URLSearchParams(); for (const [key, value] of Object.entries(data)) { if (typeof value === 'object') { params.append(key, JSON.stringify(value)); } else { params.append(key, String(value)); } } return params.toString(); } }