import qrcode from 'qrcode'; import { Cookie } from 'cookiejar'; import request from '../utils/request'; import baseRequest from '../utils/base.request'; import superagent from 'superagent'; import qs from 'querystring'; import { createKey as genKey } from './keytool'; import { getMACAdress } from '../utils/util'; import { setCliConfig } from './config'; const IDE_HOST = process.env.IDE_HOST || 'https://ideservice.alipay.com'; interface LoginOptions { width?: number; } interface CreateKeyOptions { save: boolean; } const LOGIN_PARAM = { terminalType: 'WEB', terminalVersion: 1.0, mac: getMACAdress(), os: 'herbox', osVersion: 'herbox', osArch: 'herbox', }; let qrCodeId: any = null; let HEADERS: any = null; let COOKIE: any = null; let PRIVATE_KEY = ''; // 私钥 let PUBLIC_KEY = ''; // 公钥 let TOOL_ID = ''; // toolId /** * 处理接口返回的数据 * 后端接口是gbk编码,需要重新decode一下 * */ // function handleResData(res) { // const { body, headers } = res; // const contentType = headers['content-type'] || ''; // const matched = contentType.match(/charset=(.+)/); // const charset = matched ? matched[1] : 'utf8'; // return JSON.parse(iconv.decode(Buffer.from(body, 'utf8'), charset)); // } function getCookie(cookies: string[], protocol: string) { const list: string[] = []; cookies.forEach((cookie) => { const obj = new Cookie(cookie); if (protocol === 'https') { if (obj.name === 'ALIPAYJSESSIONID' && !obj.secure) { return; } if (obj.name === 'JSESSIONID' && !obj.secure) { return; } } else { if (obj.name === 'ALIPAYJSESSIONID' && !obj.noscript) { return; } if (obj.name === 'JSESSIONID' && obj.path !== '/') { return; } } list.push(obj.toValueString()); }); return list.join('; '); } async function queryScanResult(): Promise { try { const result: any = await baseRequest({ method: 'GET', path: '/ide/login/query.json', data: { version: 1, ...LOGIN_PARAM, qrCodeId, }, }); const { body, headers } = result; const { data } = body || {}; const { status } = data || ''; if (status === 'CONFIRM') { const origin = headers['access-control-allow-origin']; const protocol = origin.startsWith('https') ? 'https' : 'http'; const cookie = getCookie(headers['set-cookie'], protocol); HEADERS = headers; COOKIE = cookie; HEADERS = headers; return { stat: 'done', }; } if (status === 'SCAN') { return { stat: 'loading', }; } if (status === 'CANCEL') { throw new Error('授权登录已取消'); } if (status === 'FAILED') { throw new Error('授权登录失败'); } // init未扫码 return { stat: 'init', }; } catch (e) { const message = e.message || '获取登录状态失败'; console.log(message); return { stat: 'fail', message, }; } } function checkScanState() { let loopTimer; let loopNum = 0; // 最多轮训60次 const MAX_LOOP_NUM = 60; return new Promise((resolve, reject) => { async function loopQueryResult() { if (loopNum === MAX_LOOP_NUM) { clearInterval(loopTimer); loopTimer = null; reject(new Error('登录超时,请重试。')); } loopNum += 1; const result = await queryScanResult(); if (result.stat === 'done') { clearInterval(loopTimer); loopTimer = null; resolve(true); } if (result.stat === 'fail') { clearInterval(loopTimer); loopTimer = null; reject(new Error(result.message)); } } function startLoop() { if (loopTimer) { return; } loopTimer = setInterval(loopQueryResult, 1000); } // 1s后开始轮训登录结果 setTimeout(startLoop, 1000); }); } async function generateKey() { try { const keyMap: any = await genKey(); const { publicKey, privateKey } = keyMap; PRIVATE_KEY = privateKey || ''; PUBLIC_KEY = publicKey || ''; } catch (e) { console.error('生成密钥后失败'); throw e; } } async function generateToolId() { try { // 在某些windows机器上,使用 got 库请求可能会没有返回,没有报错并且进程退出。。原因未知 const p = superagent.get( `${IDE_HOST}/devflow/api/configToolkey.json?${qs.stringify({ pubKey: PUBLIC_KEY, status: 1, ipWhitelist: [], whitelistStatus: 0, })}` ); const proxy = IDE_HOST.startsWith('https') ? process.env.HTTPS_PROXY : process.env.HTTP_PROXY; if (proxy) { p.proxy(proxy); } const headers = { ...HEADERS, referer: IDE_HOST, origin: IDE_HOST, cookie: COOKIE, }; Object.keys(headers).forEach((name) => { p.set(name, headers[name]); }); const result = await p; const bodyJson = result.body; if (bodyJson.stat === 'ok') { const { toolId } = bodyJson.data || {}; TOOL_ID = toolId; } else { console.log('生成密钥失败', bodyJson); throw new Error(bodyJson.msg); } } catch (e) { console.log('生成密钥失败'); throw e; } } async function create(options?: CreateKeyOptions) { await checkScanState(); // 生成密钥 await generateKey(); // 获取toolId await generateToolId(); if (options && options.save) { setCliConfig({ privateKey: PRIVATE_KEY, toolId: TOOL_ID, }); } return { privateKey: PRIVATE_KEY, toolId: TOOL_ID, }; } async function initLoginAction(options?: LoginOptions) { try { const result: any = await request({ method: 'GET', path: '/ide/login/init.json', data: { ...LOGIN_PARAM, }, }); const { qrCodeUrl, qrCodeId: id } = result; qrCodeId = id; return new Promise((resolve) => { qrcode.toDataURL( qrCodeUrl, { width: options && options.width ? options.width : 300 }, function (err, loginImageURL) { if (err) { throw new Error('二维码生成失败'); } resolve({ loginImageURL, create }); } ); }); } catch (e) { console.log('获取登录二维码失败'); throw e; } } export async function login(options?: LoginOptions) { return await initLoginAction(options); }