85 lines
2.9 KiB
JavaScript
85 lines
2.9 KiB
JavaScript
/**
|
|
* 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();
|
|
}
|
|
}
|