import { execSync } from 'node:child_process'; const API = process.env.SMOKE_API ?? 'http://localhost:3000/api/v1'; const REDIS_CONTAINER = process.env.REDIS_CONTAINER ?? 'dukang-v1-redis'; function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } async function fetchJson(path, options = {}) { const res = await fetch(`${API}${path}`, options); return res.json(); } function readSmsCodeFromRedis(phone, scene) { const key = `dukang:sms:${scene}:${phone}`; try { const code = execSync(`docker exec ${REDIS_CONTAINER} redis-cli GET "${key}"`, { encoding: 'utf8', }).trim(); if (code && code !== '(nil)') return code; } catch { /* docker/redis unavailable */ } return null; } export async function loadClientConfig() { const json = await fetchJson('/common/client-config'); if (json.code !== 0) throw new Error(`client-config: ${json.message}`); return json.data; } export async function resolveSmsCode(phone, scene) { const fromRedis = readSmsCodeFromRedis(phone, scene); if (fromRedis) return fromRedis; throw new Error( `SMS code not found for ${phone} (${scene}); send SMS first and ensure Redis is reachable`, ); } export async function sendSmsOnce(clientApp, sendPath, phone, scene) { const headers = { 'Content-Type': 'application/json', 'X-Client-App': clientApp, }; const res = await fetch(`${API}${sendPath}`, { method: 'POST', headers, body: JSON.stringify({ phone, scene }), }); return res.json(); } export async function sendSmsWithCooldown(clientApp, sendPath, phone, scene) { let json = await sendSmsOnce(clientApp, sendPath, phone, scene); if (json.code !== 0 && String(json.message).includes('过于频繁')) { await sleep(65_000); json = await sendSmsOnce(clientApp, sendPath, phone, scene); } if (json.code !== 0) { const existing = readSmsCodeFromRedis(phone, scene); if (existing) return { reusedCode: true }; throw new Error(`${sendPath}: ${json.message}`); } return json.data; } export async function loginWithSms(clientApp, phone, scene, loginPath, sendPath) { await sendSmsWithCooldown(clientApp, sendPath, phone, scene); const code = await resolveSmsCode(phone, scene); const headers = { 'Content-Type': 'application/json', 'X-Client-App': clientApp, }; const res = await fetch(`${API}${loginPath}`, { method: 'POST', headers, body: JSON.stringify({ phone, code }), }); const json = await res.json(); if (json.code !== 0) throw new Error(`${loginPath}: ${json.message}`); return json.data; }