ksp-pmt-server/src/helpers.js

106 lines
3.3 KiB
JavaScript

import crypto from 'crypto';
import fetch from 'node-fetch';
import { DEVICE, APP, UA_NATIVE } from './config.js';
import { computeTokenSnMac, computeXSign } from './crypto.js';
// ─── Utilities ───
export const generateUUID = () => crypto.randomUUID().toUpperCase();
export const nowISO = () => {
const d = new Date();
const off = -d.getTimezoneOffset();
const sign = off >= 0 ? '+' : '-';
const hh = String(Math.floor(Math.abs(off) / 60)).padStart(2, '0');
const mm = String(Math.abs(off) % 60).padStart(2, '0');
return (
d
.toISOString()
.replace('Z', '')
.replace(/\.\d{3}/, `.${String(d.getMilliseconds()).padStart(3, '0')}`) +
sign +
hh +
mm
);
};
// ─── Cookie builder ───
export const entranceCookie = (extraUserToken) => {
let c = `deviceId=${DEVICE.deviceId}; installId=${DEVICE.installId}; is_mobile_app=true; locale=${APP.locale}; ma_bld=${APP.build}; ma_platform_type=${APP.platform}; ma_platform_ver=${APP.platformVer}; ma_ver=${APP.version}; pk=${DEVICE.pk}; pkTag=${DEVICE.pkTag}; xs=R:0|E:0|RH:0|N:0`;
if (extraUserToken) c += `; user_token=${extraUserToken}`;
return c;
};
// ─── Extract user_token from set-cookie ───
export const extractUserToken = (resp) => {
const raw = resp.headers.raw()['set-cookie'] || [];
for (const c of raw) {
const m = c.match(/user_token=([^;]+)/);
if (m) return m[1];
}
return null;
};
// ─── Logged fetch wrapper ───
export const loggedFetch = async (url, options = {}) => {
const method = (options.method || 'GET').toUpperCase();
console.log(`\n>>> ${method} ${url}`);
if (options.headers) console.log('>>> Headers:', JSON.stringify(options.headers, null, 2));
if (options.body) {
try {
console.log('>>> Body:', JSON.parse(options.body));
} catch {
console.log('>>> Body:', options.body);
}
}
const resp = await fetch(url, options);
const cloned = resp.clone();
let body;
try {
body = await cloned.json();
} catch {
try {
body = await cloned.text();
} catch {
body = '[unreadable]';
}
}
console.log(`<<< ${resp.status} ${resp.statusText}`);
console.log('<<< Response:', typeof body === 'object' ? JSON.stringify(body, null, 2) : body);
return resp;
};
// ─── Signed QR-pay headers (session passed as parameter) ───
export const signedQrPayHeaders = (url, session) => {
const xsh =
'url,X-Request-ID,X-Device-ID,X-Platform-Ver,X-App-Bld,X-Time,X-Kb-TokenSn,X-App-Ver,X-Kb-TokenSnMac,X-Call,X-PI,X-Install-ID,X-Platform-Type,X-Locale,X-SV';
const headers = {
'X-Kb-TokenSn': session.tokenSN,
'X-Kb-TokenSnMac': computeTokenSnMac(session.tokenSN, session.decryptedSecret),
'X-PI': session.profileId != null ? String(session.profileId) : '',
'X-Install-ID': DEVICE.installId,
'X-Device-ID': DEVICE.deviceId,
'X-App-Ver': APP.version,
'X-App-Bld': APP.build,
'X-Platform-Type': APP.platform,
'X-Platform-Ver': APP.platformVer,
'X-Locale': APP.locale,
'X-Time': nowISO(),
'X-Request-ID': generateUUID(),
'X-Call': 'notConnected',
'X-SV': '2',
'X-SH': xsh,
'User-Agent': UA_NATIVE,
Accept: '*/*',
'Accept-Language': 'ru',
'Accept-Encoding': 'gzip, deflate, br',
};
headers['X-Sign'] = computeXSign(url, headers, xsh);
return headers;
};